Google Sheets Script won't run on Form Submit [duplicate] - google-apps-script

This question already has an answer here:
Ensure form submission trigger runs only one sheet
(1 answer)
Closed 6 years ago.
I have a Google Sheets script which copies elements of a submitted form data to another spreadsheet and then transforms some of the data for separate use. The script piece works perfectly when I run it manually, but I want it to run automatically.
There are a number of forms connected to this spreadsheet, all submitting data at different times.
So, I sort of have two questions:
How do I get my script to run automatically? I have created a trigger for From Spreadsheet and On Form Submit, but it never runs.
How do I make sure it runs only when: either the specific form is submitted or when the specific sheet has a new row added?
There were a number of articles around the web which had pieces of an answer, but many were from 3 years ago, and it seems that the way Sheets, Forms and the Script Editor work have changed much since then.

You need an onFormSubmit function along with the
installable trigger.
If you are using a Html Service Form, you can get
the name of the destination sheet from the form event
output. You can't do that with forms created from
the spreadsheet. The best I have been able to do is
to compare the event timestamp to the timestamps on
the last rows of the form response sheets to determine which
sheet was updated. This is not totally fool proof since
it is possible for mulitiple forms to be submitted at
the same time (but not likely).
Try this:
//installable trigger also needed.
function onFormSubmit(e){
var ts=e.namedValues.Timestamp
findResponseForm(ts)
}
function findResponseForm(ts) {
var ss=SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getSheetByName('Form Responses 1') //get 'Form Responses 1' sheet
var lr=s.getLastRow() //get last row number of 'Form Responses 1' sheet
var s1=ss.getSheetByName('Form Responses 2') //get 'Form Responses 2' sheet
var lr1=s1.getLastRow() //get last row number of 'Form Responses 2' sheet
var time1=s.getRange(lr, 1, 1,1).getValue() //get 'Form Responses 1'last Timestamp
var ftime1=Utilities.formatDate(time1, "GMT-6", "d/M/yyyy' 'HH:mm:ss") //convert 'Form Responses 1'last Timestamp format to form Timestamp format
var time2=s1.getRange(lr1, 1, 1,1).getValue() //get 'Form Responses 2'last Timestamp
var ftime2=Utilities.formatDate(time2, "GMT-6", "d/M/yyyy' 'HH:mm:ss") //convert 'Form Responses 1'last Timestamp format to form Timestamp format
if(ts==ftime1){ //if date/time match
Logger.log('Form Responses 1')
//call function to prosses 'Form Responses 1' data
}
else if(ts==ftime2){ //if date/time match
Logger.log('Form Responses 2')
//call function to prosses 'Form Responses 2' data
//Add more 'else if' if more forms are involved.
}
}

Related

Form responses spreadsheet.getSheets() doesn't return responses sheet?

My overall goal is to create a new 'responses' sheet (in the same Spreadsheet) each week for a Form. At the end of processing the week's responses, given the limitations on Form Responses Spreadsheets (not being able to delete rows), I try to do the following:
// ...
// Copy the existing answers to a new sheet in the spreadsheet and rename it
// with this week's DateTime.
today = Utilities.formatDate(new Date(), 'GMT', 'dd/MM/yyyy HH:mm');
currentSheet.copyTo(spreadsheet).setName(today);
// Delete the form responses from the Form. This will _not_ update the
// responses Spreadsheet.
form = FormApp.openByUrl(spreadsheet.getFormUrl());
form.deleteAllResponses();
// Begin dodgy hack
// First unlink the Form from the responses Spreadsheet
form.removeDestination();
// Delete the old responses Sheet
console.log(`Removing the ${CURRENT_SHEET_NAME} sheet`);
spreadsheet.deleteSheet(currentSheet);
// Relink the existing responses Spreadsheet to the Form
form.setDestination(FormApp.DestinationType.SPREADSHEET, SPREADSHEET_ID);
// It's not possible to get a handle on the sheet the responses will be put
// into from Form or FormApp, so there are two choices: iterate through
// the sheets and find one with the correct FormUrl, or hope that for
// all eternity, Google Apps Scripts will add new sheets at the zeroth index.
const formId = form.getId();
// **MADNESS**: only the renamed sheet comes back from getSheets. In the
// Spreadsheet UI there is now two sheets: `Form Responses N` and `today`
// where N is the ridiculous number of times I've tried to run this script
// and Today is the current DateTime.
spreadsheet.getSheets().forEach((sheet) => console.log(sheet.getName()));
// Below is the intended, currently non-functional, behaviour
const newCurrentSheet = spreadsheet.getSheets().find((sheet) => {
return (url && url.indexOf(formId) > -1);
});
newCurrentSheet.setName(CURRENT_SHEET_NAME);
I've tried the following, none of which worked:
Using getDestinationId on the Form and refetching the spreadsheet with SpreadsheetApp.openById before running getSheets
Using Utilities.sleep for 5 seconds (arbitrary number) after running .setDestination to give the API some execution/thinking time.
This feels like a bug, but perhaps there's another way to get a 'refreshed' view of the Spreadsheet after re-linking the Form? Or I've missed something really obvious (likely).
It's very likely that your script is not getting the new form responses sheet because it's missing SpreadsheetApp.flush() after that sheet was added.

Google Sheets: Action Based on Birthday

I'm trying to send myself either an email or copy the row to a new sheet when it's someone's birthday or hire date anniversary. Copying the line to a new sheet would allow me to use zapier to notify me of the update. Either would work. The sheet uses a form to collect data.
I've built a few scripts but nothing that had to do with dates. I'm just struggling with this one and have tried a few examples I could find with no luck.
Here is this sheet. It's view only so just let me know if you need more access.
I understand that you want to replicate your form responses Sheet in another Sheet (let's call it Zapier Sheet) automatically each time that a new form response is added. You can achieve that goal developing an Apps Script code that runs at each form response. In that case you can use a code similar to this one:
function so62400514() {
var formSheet = SpreadsheetApp.openById(
'{FORM SHEET ID}').getSheets()[0];
var zapierSheet = SpreadsheetApp.openById(
'{ZAPIER SHEET ID}').getSheets()[0];
var formData = formSheet.getRange(1, 1, formSheet.getLastRow(), formSheet
.getLastColumn()).getValues();
var zapierData = zapierSheet.getRange(1, 1, zapierSheet.getLastRow(),
formSheet.getLastColumn()).getValues();
var recorded = false;
for (var fr = 0; fr < formData.length; fr++) {
for (var zr = 0; zr < zapierData.length; zr++) {
if (formData[fr].toLocaleString() == zapierData[zr].toLocaleString()) {
recorded = true;
}
}
if (recorded == false) {
zapierSheet.appendRow(formData[fr]);
} else {
recorded = false;
}
}
}
This code will first open both sheets (using SpreadsheetApp.openById() and Spreadsheet.getSheets()) to select the data with Sheet.getRange (setting boundaries with Sheet.getLastRow() and Sheet.getLastColumn()) and reading it using Range.getValues(). After that operation the data will get iterated using the property Array.length as the perimeter. The iteration compares each row from the form Sheet to every row of the zapier sheet (to accomplish that, I first parsed the row as a string with Date.toLocaleString()). If the form row is found in the zapier sheet, the boolean recorded will flag to true. After every row on the zapier sheet gets compared to the form row, the code will write it down on the zapier sheet based on the boolean flag.
As explained in the previous paragraph, this code will take the form sheet rows not present in the zapier sheet; and paste them on the zapier sheet. I used this approach to prevent missing any row (as it could happen when simultaneous users answer the form all at once). To make this fire automatically you'll need to set up an installable trigger with these settings:
As an example, let's say that we have these form responses:
And our initial sample zapier sheet looks like this one below. Please, notice how several past rows are missing;
After running the script (as it will do automatically) this would be the result:
I suggest running the script manually for an initial setup. If the timestamps diverge, please check if both spreadsheets share time zones. Don't hesitate to ask me further questions to clarify my answer.

Automate dynamic IMPORTRANGE in google sheets

I am building a multi-section questionnaire (3 in total) and I want to have 4 sheets to hold the data (one master and one for each section).
How would I send data to another sheet from the master spreadsheet when a new row is added to the Master Sheet and make it dynamic so it does not pull the same row everytime?
I have found this script online:
script link
but it is for moving data between sheets unfortunately. Could it be remodeled?
Thanks!
Perhaps the setFormula class can help you, it can be dynamic & you can update it with a trigger
sheet.getRange(1,1,1,1).setFormula('=IMPORTRANGE("SPREADSHEET_URL", "SHEET_NAME!'+rangevariable1inA1notation+':'+rangevariable2inA1notation+'")');
Make sure you get the range you want, and the notation of those 2 variables to get the dynamic range:
var rangevariable1inA1notation = sheet.getRange(1,1).getA1Notation(); // for example, instead of 1,1 it could be your own variable
var rangevariable2inA1notation = sheet.getRange(2,2).getA1Notation(); //for example
To send values to another sheet (besides from your main one), you can use the onFormSubmit function, it will be triggered every time the form is submitted and with its event object, which contains the information from your form, you will be able to pass those values into the other sheets as you want.
// This will be triggered every time the form is submitted
function onFormSubmit(e) {
// Get all sheets in the active spreadsheet
var sheetsArr = SpreadsheetApp.getActiveSpreadsheet().getSheets();
// Get the second sheet
var slaveSheet1 = sheetsArr[1];
// Get the row where the values will be inserted
var rowVals = slaveSheet1.getLastRow() + 1;
// The number of cols where you will puth the values
var numberOfCols = e.values.length;
// Set values that came from the form
slaveSheet1.getRange(rowVals, 1, 1, numberOfCols).setValues([e.values]);
}
Be careful with the installable triggers Restrictions.

Google Apps - on Form Submit - what is the name of the sheet

The following code from Alex's code on Google Forms onsubmit
will send an email when a form attached to the spreadsheet is submitted.
function onSpreadsheetSubmit(e) {
var row = e.range.getRow();
MailApp.sendEmail("me#example.com",
"Your subject, rows: "+ row,
"A new application has been submitted on row: "+
row,
{name:"From your friendly spreadsheet"});
}
The code successfully returns the row number of the submission using e.range.getRow()
How can I get the name of the sheet that the form is connected to?
E.g. something like e.range.getSheet()
Although this question How to get form values in the submit event handler? discusses retrieving the event values, I do not believe that it addresses directly the retrieval of the name of the sheet.
And how can I elegantly get the data of the new row?
If all you're trying to do is return the sheet name from your event object, use:
var name = e.range.getSheet().getName();
To access the values submitted to the form, you can use:
var values = e.values;
This returns the submitted values in an array.
There are a lot of possibilities with event objects, you should really look into the documentation to find out how to use them to your advantage.
References:
Event Objects documentation.

How to merge a large number of form response sheets into one master sheet. (Google Sheets)

I have a spreadsheet in which I have the form response sheets from a large number of forms (quizzes). As all the forms contain the same format, number and order of questions I want to collect them all into one sheet.
I know this can be done using the query({sheet1!range,sheet2!range...}) But the size of this query would be huge (have over 25 forms!) and it would require me to fiddle around with this formula every time I add a new form.
What i initially investigated was creating a list of sheets in a range and then tried to get arrayformula query to run through that list using indirect. This however did not work and after asking on this forum have been told that that cannot be done.
I was advised to look into scripts and have spent all weekend trying to find a script that can do this. I have however failed.
I need the script to copy the last row of a form response sheet to the bottom of a master sheet. I would like the script to do this to all response sheets (I have a naming format for sheets that would allow the script to easily see which sheets to incorporate). I would imagine I need to use the onSubmit() function but not sure.
Overview
I don't think that using a on submit trigger is a good idea because there are several forms involved and a large number of responses. By the other hand could be conflicts if several responses are submitted very close. IMHO a better approach is to run the script by demand (manually) or by using a time-driven trigger.
As Google Apps Script execution time should not exceed six minutes1, instead of checking each sheet for new responses I think that a better approach is to clear the master sheet and append all the sheet responses at once. This could work if there will no be notes, comments, custom formats, data validations or data changes applied directly on the master sheet.
Script
Below is a script that joins the content of form responses sheets that are in the same spreadsheet to a sheet named 'Master'. It could be easily adapted, I think.
It's assumed that the spreadsheet only contains form responses sheets and the 'Master' sheet.
/**
* Joins the data from form responses sheets on a master responses sheet.
* Assumes that the master sheet has the form response headers and
* that there aren't extra columns.
*/
function joinSheetsData() {
// Initialize loop variables
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var masterName = 'Master'; // Change this.
var masterSheet = ss.getSheetByName(masterName);
var headers = [masterSheet.getDataRange().getValues()[0]];
var masterdata = [];
// Loop to append form responses from all response sheets to one array.
for(var i = 0; i < sheets.length; i++){
if(sheets[i].getName() != masterSheet.getName()){
var data = sheets[i].getDataRange().getValues();
data.splice(0,1);
masterdata = append(masterdata,data);
}
}
// Clear the master sheet.
masterSheet.clear();
// Add the headers to the master sheet.
var masterheadersRow = masterSheet.getRange(1, 1, 1,
masterdata[0].length);
masterheadersRow.setValues(headers);
// Send the resulting array to the master sheet.
var masterdataRange = masterSheet.getRange(2, 1, masterdata.length,
masterdata[0].length);
masterdataRange.setValues(masterdata);
}
/*
* Auxiliary function to append 2D arrays.
*/
function append(a,b){
for(var i = 0; i<b.length; i++){
a.push(b[i]);
}
return a;
}