Copying google apps script with forms - google-apps-script

I'm running into a strange issue that is causing me considerable difficulty hoped someone her might have some insight.
I have a form that is accepting responses and sending the results to a sheet. I've written an additional onsubmit script that then marks up some other sheets both in order to facilitate tracking and to provide a prefilled url that can be sent next time the form needs to be submitted.
The forms are for student data collection and I therefor need three each collecting data about students in different years. My issue is that I can't seem to copy the scripts to the other forms without them ceasing to work.
I have taken the following approaches:
Initially I just tried duplicating the entire form and then changing it, this meant the scripts came along with the copy.
I then tried keeping the duplicated form but deleting out all the script information and copy and pasting the script to a new project within the form.
I then tried creating a new test form and copy and pasting the script to that - again no success.
All attempts have resulted in the data being successfully submitted to the responses sheet but the script either not triggering or failing silently.
I can't see anything in the script I have produced that would cause it to work only on one form but I'm not very experienced in JS and it may be I am not seeing something obvious ( I am aware there are some foolish bits in the script but this is the version I know works).
If anyone can point out anything that would make the script not work for any other forms or can explain how the script can be successfully copied I'd be really grateful.
Code:
/* onsubmit function that creates a prefilled url and prints to sheet */
function onSubmit(e) {
var subject = "ICT";
//generate url
var url=e.response.toPrefilledUrl();
var responses = e.response;//e is of type formresponse.
var name = responses.getItemResponses()[0].getResponse();
var sheet1 = SpreadsheetApp.openByUrl("https://docs.google.com/a/rbair.org.uk/spreadsheets/d/1w_rCPJR-O9_fUs1T5HKmaTUsRjk_9JRVRPxV2kJzNMk/edit#gid=0").getSheetByName("Admin");
update(name, subject);
//print to cell in feedback sheet
var data = sheet1.getDataRange().getValues();
for(i=0; i<100; i++)
{
if(data[i][1]==name)
{
sheet1.getRange(i+1, 3).setValue(url);
}
}
}
function test()
{
var name = "Piers Hartley";
var subject = "ICT";
update(name, subject);
}
function update (name, subject)
{
//var name = "Piers Hartley";
//var subject = "ICT";
var msheet = SpreadsheetApp.openByUrl("https://docs.google.com/a/rbair.org.uk/spreadsheets/d/1IauIkNCrE95qNAL2KxfJhespEcRWuMXYGzkkjwFnezg/edit#gid=0");
var refsheet = msheet.getSheetByName("Subject Teachers");
var track = refsheet.getRange(2,10).getValue();
var tsheet = msheet.getSheetByName(track);
var trdr = tsheet.getDataRange();
var trdata = trdr.getValues();
var lcol = trdr.getLastColumn();
var lrow = trdr.getLastRow();
for(i=0; i<lcol; i++)
{
if (trdata [0] [i] == subject)
{
for (j=1; j<lrow; j++)
{
if (trdata [j] [i] == name)
{
tsheet.getRange(i+2, j+1).setValue("Done");
}
}
}
}
}

Have you added the On Form Submit trigger? (under Resources in the script editor).

Related

How do I use apps script to programmatically create/submit a google form response on a google form that collects emails? [duplicate]

I have the following issue. I am trying to create a script that will autofill a template google document using the submission of a google form. I am able to get the script to work for questions that are input with text but am struggling on getting the data from questions in the form that are checkboxes (or multiple choice) to work and fill the google document. Any assistance would be great. For example the variable identified as "offense" is from a question with checkboxes that has about 30 different options, I would like each option that is checked on the form to replace text within my google doc. Thanks.
function autoFillGoogleDocFromForm(e) {
//e.values is an array of form values
var timestamp = e.values[4];
var studentName = e.values[3];
var oe = e.values[16];
var gradelevel = e.values[14];
var program = e.values[15];
var offense = e.values[6];
var action = e.values[18];
var serve = e.values[31];
var makeUp = e.values[32];
var comments = e.values[29];
//file is the template file, and you get it by ID
var file = DriveApp.getFileById('1nPWC0IKc1zUJXYxbGahJsSW4uNWwhxnLM8shcD8kEE4');
//We can make a copy of the template, name it, and optionally tell it what folder to live in
//file.makeCopy will return a Google Drive file object
var folder = DriveApp.getFolderById('1FlpHRKqYrEHttA-3ozU3oUVJlgiqqa-F')
var copy = file.makeCopy(studentName + ', ' + timestamp, folder);
//Once we've got the new file created, we need to open it as a document by using its ID
var doc = DocumentApp.openById(copy.getId());
//Since everything we need to change is in the body, we need to get that
var body = doc.getBody();
//Then we call all of our replaceText methods
body.replaceText('<<Student Name>>', studentName);
body.replaceText('<<Incident Date>>', timestamp);
body.replaceText('<<Student Grade>>', gradelevel);
body.replaceText('<<Open enrolled?>>', oe);
body.replaceText('<<IEP/504?>>', program);
body.replaceText('<<Reason for Referral (Handbook)>>', offense);
body.replaceText('<<Administrative Action>>', action);
body.replaceText('<<Date(s) to be Served>>', serve);
body.replaceText('<<Make up Date(s)>>', makeUp);
body.replaceText('<<Comments>>', comments);
//Lastly we save and close the document to persist our changes
doc.saveAndClose();
}
You need to use the labels assigned to the checkboxes to determine if they have been checked. Same for multiple coice.
You can't use ListItems because you can't set the glyph to a check box so I simply insert text with a checkbox character.
I created a form
I then created an onFormSubmit(e) installed trigger in the spreadsheet to get the form response and put it in the Doc. Here I've simply used an active doc to perform my tests. You will need to adjust the script to handle your template doc.
function onFormSubmit() {
// test data
let e = {"authMode":"FULL","namedValues":{"Timestamp":["8/16/2022 14:40:26"],"Student Grade":["Junior"],"Reason for Referrel":["Bad grades, Disruptive in class, Other"],"Student Name":["Joe Smith"],"Open Enrollment":["Yes"]},"range":{"columnEnd":5,"columnStart":1,"rowEnd":2,"rowStart":2},"source":{},"triggerUid":"12151926","values":["8/16/2022 14:40:26","Joe Smith","Junior","Yes","Bad grades, Disruptive in class, Other"]};
try {
let doc = DocumentApp.getActiveDocument();
let body = doc.getBody();
let referrels = ["Bad grades","Unexcused absence","Disruptive in class","Fighting","Other"];
body.replaceText("<<Student Name>>",e.namedValues["Student Name"]);
body.replaceText("<<Student Grade>>",e.namedValues["Student Grade"]);
body.replaceText("<<Open Enrollment>>",e.namedValues["Open Enrollment"]);
// Notice the regex expression below because findText doesn't seem to handle parenthesis well
let text = body.findText("<<Reason for Referral.*>>");
body.replaceText("<<Reason for Referral.*>>","");
if( text ) {
let index = body.getChildIndex(text.getElement().getParent())+1;
referrels.forEach( item => {
let checked = e.namedValues["Reason for Referrel"][0].indexOf(item);
if( checked >= 0 ) {
let listItem = body.insertListItem(index,item);
index = body.getChildIndex(listItem)+1;
}
}
);
}
}
catch(err) {
Logger.log(err);
}
}

Google sheet sends an email when a new form is submitted if a question has a particular answer

I hope you can help, I have tried to find a solution but I am unable to find one that resolves my issue.
I have a form that collects information if a student request a change to their course, we are a multi school site and depending on the site, a different member of staff is required to approve the change.
Part of the information collect is the site code OAAd for example, and this information is held in column F.
If a submission is received the 'On form submit' trigger, triggers the script. Currently it doesn't seem to work, yet it triggers without error. If I change the trigger 'On edit' and edit a cell in column F it works fine.
eventually I will use ifElse to add in the additional options for each site, but if I could get it to work for one site that would be great.
function sendEmailapproval(e) {
if(e.range.getColumn()==6 && e.value=='OAAd'){
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email Addresses").getRange("B3");
var recipent = emailRange.getValue();
var html = HtmlService.createTemplateFromFile("Email1.html");
var htmlText = html.evaluate().getContent();
var subject = "New Change Log Request Post 16";
var body = "";
var options = { htmlBody: htmlText}
MailApp.sendEmail(recipent, subject, body, options)
}
}
Background
There are 2 options to add an onFormSubmit trigger:
Using the form settings / script.
Using the attached spreadsheet script.
Both methods gives an event object to communicate with new submmited responese, but threre are differaces between those objet so you need to dicide in advance which method to choose.
You can read more about this topic here.
For your propose, would preder to use the spreadsheet script, since it easier to extract specific values from it.
Solution
First, Here is your correct sendEmailapproval function:
function sendEmailapproval(e) {
const namedValues = e.namedValues
const question = 'Question 3'; // copy-paste from the form
switch (namedValues[question][0])
{
case 'OAAd':
const emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email Addresses").getRange("B3");
const recipent = emailRange.getValue();
const html = HtmlService.createTemplateFromFile("Email1.html");
const htmlText = html.evaluate().getContent();
const subject = "New Change Log Request Post 16";
const body = "";
const options = { htmlBody: htmlText}
MailApp.sendEmail(recipent, subject, body, options)
break;
// case 'SOMETHING_ELSE':
// break;
default:
}
}
Note that e.namedValues is an object, where each key mapped to an array.
So namedValues[question][0] refers to the string value of namedValues[question].
Second, add a trigger to the spreadsheet that attached to the form.
function createTrigger() {
const sheet = SpreadsheetApp.openById("YOUR_SPREADSHEET_ID").getSheetByName("THE_SHEET_NAME");
ScriptApp.newTrigger("sendEmailapproval")
.forSpreadsheet(sheet)
.onFormSubmit()
.create();
}

Using Google Apps Script to get responses from multiple forms and send an email once to each respondent

I have been puzzling this over for some time and have searched extensively, but found no solution.
I'm using Google Apps Script and I run events for a large organization and we have about 80 different registration Google Forms for these events. I want to get the registrant's email address and send them an email when they submit their form. This is easy to accomplish by setting up each individual form. Ideally, I would set up the onSubmit trigger for the form and then copy that form for each new event. However, it seems you cannot install a trigger programmatically without going to the form and running the script manually, and then authorize it. When a form is copied it also loses all its triggers. Am I wrong about this? Doing this for each form is not realistic given that events are added all the time and I have other responsibilities.
Is there no way to set a trigger for these files without running and authorizing each one?
My other solution is:
I am trying to get all the forms in a folder and then get the responses and send a single email to each registrant. This seems overly complicated and requires checking all the forms regularly since there are no triggers for the individual forms. I tried setting triggers in my spreadsheet for the forms and this works, but the number of triggers for a spreadsheet is limited to 20, so doesn't work here. Running my script every minute and then checking if an email has been sent to each respondent seems possible, but complex and possibly prone to errors...
Thanks for any help you can offer!
pgSystemTester's answer worked for me.
I added two bits of code.
One, to declare the time stamp value to zero if there wasn't one
there.
Two, the code needed a "-1" when you get dRange or you insert a new
row which each run.
function sendEmailsCalendarInvite() {
const dRange = sheet.getRange(2, registrationFormIdId, sheet.getLastRow() - 1, 2);
var theList = dRange.getValues();
for (i = 0; i < theList.length; i++) {
if (theList [i][1] == ''){
theList[i][1] = 0;
}
if (theList[i][0] != '') {
var aForm = FormApp.openById(theList[i][0]);
var latestReply = theList[i][1];
var allResponses = aForm.getResponses();
for (var r = 0; r < allResponses.length; r++) {
var aResponse = allResponses[r];
var rTime = aResponse.getTimestamp();
if (rTime > theList[i][1]) {
//run procedure on response using aForm and aResponse variables
console.log('If ran')
if (rTime > latestReply) {
//updates latest timestamp if needed
latestReply = rTime;
}
//next reply
}
}
theList[i][1] = latestReply;
//next form
}
}
//updates timestamps
dRange.setValues(theList);
}
This is probably a simple solution that will work. Setup a spreadsheet that holds all Form ID's you wish to check and then a corresponding latest response. Then set this below trigger to run every ten minutes or so.
const ss = SpreadsheetApp.getActiveSheet();
const dRange = ss.getRange(2,1,ss.getLastRow(),2 );
function loopFormsOnSheet() {
var theList = dRange.getValues();
for(i=0;i<theList.length;i++){
if(theList[i][0]!=''){
var aForm = FormApp.openById(theList[i][0]);
var latestReply = theList[i][1];
var allResponses = aForm.getResponses();
for(var r=0;r<allResponses.length;r++){
var aResponse = allResponses[r];
var rTime = aResponse.getTimestamp();
if(rTime > theList[i][1]){
//run procedure on response using aForm and aResponse variables
if(rTime >latestReply){
//updates latest timestamp if needed
latestReply=rTime;
}
//next reply
}
}
theList[i][1] = latestReply;
//next form
}
}
//updates timestamps
dRange.setValues(theList);
}

How do I send an automated email to a specific person, depending on task status, using an aux sheet to store emails?

Gory title but I couldn't find a way of being clearer.
I have no experience with coding and I was wondering if doing something like what I'm about to explain would be possible.
This is my example sheet:
What I'm looking to do is to have automated emails sent out to the person assigned to the task if the task status is set to urgent, while referencing people by names and having an auxiliary sheet with all the names and corresponding emails.
I've browsed around and found some similar questions which I unfortunately had no success in adapting. The one thing I got is that I need to setup an onEdit trigger, which I've done, but I'm completely clueless from here on out.
Can someone point me in the right direction? I don't have a clue where to start.
Looking forward to hearing your advice.
Thanks and stay safe in these crazy times!
It was a funny exercise. I tried to make the script as clean and reusable as possible for others to be able to adapt it to their needs.
Usage
Open spreadsheet you want to add script to.
Open Script Editor: Tools / Script editor.
Add the code. It can be configured by adjusting variables in the top:
var trackerSheetName = 'Tracker 1'
var trackerSheetStatusColumnIndex = 2
var trackerSheetNameColumnIndex = 4
var triggeringStatusValue = 'Urgent'
var peopleSheetName = 'AUX'
var peopleSheetNameColumnIndex = 1
var peopleSheetEmailColumnIndex = 2
var emailSubject = 'We need your attention'
var emailBody = 'It is urgent'
function checkStatusUpdate(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var activeSheet = spreadsheet.getActiveSheet()
// skip if different sheet edited
if (activeSheet.getName() !== trackerSheetName) {
return
}
var editedRange = e.range
// skip if not a single cell edit
if (editedRange.columnStart !== editedRange.columnEnd || editedRange.rowStart !== editedRange.rowEnd) {
return
}
// skip if edited cell is not from Status column
if (editedRange.columnStart !== trackerSheetStatusColumnIndex) {
return
}
// skip if Status changed to something other than we're looking for
if (e.value !== triggeringStatusValue) {
return
}
var assigneeName = activeSheet.getRange(editedRange.rowStart, trackerSheetNameColumnIndex, 1, 1).getValue()
var peopleSheet = spreadsheet.getSheetByName(peopleSheetName)
var people = peopleSheet.getRange(2, 1, peopleSheet.getMaxRows(), peopleSheet.getMaxColumns()).getValues()
// filter out empty rows
people.filter(function (person) {
return person[peopleSheetNameColumnIndex - 1] && person[peopleSheetEmailColumnIndex - 1]
}).forEach(function (person) {
if (person[peopleSheetNameColumnIndex - 1] === assigneeName) {
var email = person[peopleSheetEmailColumnIndex - 1]
MailApp.sendEmail(email, emailSubject, emailBody)
}
})
}
Save the code in editor.
Open Installable Triggers page: Edit / Current project's triggers.
Create a new trigger. Set Event Type to On edit. Keep other options default.
Save the Trigger and confirm granting the script permissions to access spreadsheets and send email on your behalf.
Go back to your spreadsheet and try changing status in Tracker 1 tab for any of the rows. Corresponding recipient should receive an email shortly.
This should get you started:
You will need to create an installable trigger for onMyEdit function. The dialog will help you to design you email by giving you an html format to display it. When you're ready just comment out the dialog and remove the // from in front of the GmailApp.sendEdmail() line.
function onMyEdit(e) {
//e.source.toast('Entry');
const sh=e.range.getSheet();
if(sh.getName()=="Tracker") {
if(e.range.columnStart==2 && e.value=='Urgent') {
//e.source.toast('flag1');
const title=e.range.offset(0,-1).getValue();
const desc=e.range.offset(0,1).getValue();
const comm=e.range.offset(0,3).getValue();
if(title && desc) {
var html=Utilities.formatString('<br />Task Title:%s<br />Desc:%s<br />Comments:%s',title,desc,comm?comm:"No Additional Comments");
//GmailApp.sendEmail(e.range.offset(0,2).getValue(), "Urgent Message from Tracker", '',{htmlBody:html});
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html).setWidth(600), 'Tracker Message');
e.source.toast('Email Sent');
}else{
e.source.toast('Missing Inputs');
}
}
}
}
GmailApp.sendEmail()

Script onForm Submit trigger not working properly

I created a google form that will take the latest response and move the selected choice to the other section. So if someone checks out a laptop, once they submit the form the laptop choice will appear in the check in section. When I manually run the script it works perfectly fine but once I add the trigger it works for the first few times then it starts creating multiple triggers for one submission which then creates multiple new checkboxes on the form that all say the same thing. Like for example I'll have three different laptop choices when there should only be one. So I had to take the trigger off and I've looked at other similar questions about this problem but they all involve spreadsheets but mine is purely working with the google form so I'm not sure if those solutions will work for me.
I didn't add all my code since part of it is the same thing just with different variables for moving choices from check in to checkout.
var form = FormApp.openById('1I5uMesHbeVZ_RSP8wxmmpPA7-Sgcc4b6dzzH305c8K8');
/**
Responds to a form submission event when the on formSubmit trigger is
enabled
*
* #param {Event} e The event parameter created by a form submission
*/
//function that gets checkout responses
function myFunction(e) {
//getting form responses
var formResponses = form.getResponses();
//getting latest response
var latestFR = formResponses[form.getResponses().length-1];
//getting the item/question responses, checkout check in
var itemResponses = latestFR.getItemResponses();
//looping through item responses to see which item has a response
for (var i=0; i<itemResponses.length; i++) {
switch (itemResponses[i].getItem().getTitle()) {
//if only response to checkout
case "Checkout":
var outAnswer = itemResponses[i].getResponse();
outAnswer.forEach(addOut);
outAnswer.forEach(deleteOut);
break;
//if only response to check in
case "Check In":
var inAnswer = itemResponses[i].getResponse();
inAnswer.forEach(addToCheckOut);
inAnswer.forEach(deleteIn);
break;
//if response to both check out/in
case "Checkout" && "Check In":
var outAnswer = itemResponses[i].getResponse();
var inAnswer = itemResponses[i].getResponse();
outAnswer.forEach(addOut);
outAnswer.forEach(deleteOut);
inAnswer.forEach(addToCheckOut);
inAnswer.forEach(deleteIn);
break;
}}
//getting email response to send email
var email = itemResponses[0].getResponse();
//testing to see if it gets the latest submission
//delete my email later
var subject = 'Response';
var emailTo = [email];
var body = 'Response is' + outAnswer + inAnswer;
MailApp.sendEmail(emailTo, subject, body, {
htmlBody: body});
}
//function that adds the latest response from checkout to check in
section
function addOut(outAnswer) {
//getting check in section item with its choices
var a = form.getItems(FormApp.ItemType.CHECKBOX)[1].asCheckboxItem();
//getting choices from check in
var choices = a.getChoices();
//creating new choice for check in
var choice = a.createChoice(outAnswer);
//adding the choice to the choices
choices.push(choice);
//setting the choices with new choice for check in
a.setChoices(choices);
}
//function that deletes answer from checkout
//only works when its a string so convert outAnswer to string value with
toString but only works with a single choice
function deleteOut(outAnswer) {
var del = form.getItems(FormApp.ItemType.CHECKBOX)
[0].asCheckboxItem();
del.setChoices(del.getChoices().filter(function (choice) {
return choice.getValue() !== outAnswer.toString(); }));
}
You're going to need to do the same kind of thing as the spreadsheet answers suggested, create a script lock and use it to dump quick successive triggers.
Just add the following lines to the top of your script:
var lock = LockService.getScriptLock();
try {
lock.waitLock(3000);
} catch (e) {Logger.log('Could not obtain lock after 3 seconds.');}
Utilities.sleep(3000);
You can also add a "lock.releaseLock();" to the end of your script, but it isn't necessary, locks release on their own.
All this code does is reject any new submissions in the next three seconds after it is triggered. If that isn't enough, change the time in the waitlock AND the sleep to 5000 (forms generally take less than three seconds to run a script like this so you are forcing the script to take longer).