Google Forms needing post processing writing to a Google Sheet - google-apps-script

We're using a Google Form which writes to a Google Spreadsheet. I would like to do some post processing of the data as it enters the datasource.
I have the following code started. Is there a way to implicity obtain a reference to the active spreadsheet or do I need to modify it to have a hard reference to the id?
Is it possible to intercept a datastream and modify values before arriving at the Google Sheet?
function onFormSubmit(event) {
// This function will be called everytime the form is submitted.
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 1').getRange(SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 1').getLastRow(), 4).setBackground('#4287f5');
}

Solution:
You simply need to take advantage of the event object:
function onFormSubmit(event) {
const ss = event.source;
const as = ss.getActiveSheet();
if (as.getName()=='Form Responses 1'){
as.getRange(as.getLastRow(),4).setBackground('#4287f5');
}
}
Please Note:
There are two onFormSubmit triggers: one for google sheets and one for google forms. In your case, since you want to edit something in the spreadsheet, it makes more sense to use the first one.
Both are installable triggers which means that you need to install it either by a script or manually. To install it manually you can click on project's triggers, create a trigger, select onFormSubmit as the function and event type On form submit.

Related

How to wait for Google Form script to finish execution before executing Google sheet script in sheet linked to the form

I have two scripts. One linked to a Google form and another linked to Google sheets. I need to make sure the Google Form script finishes execution before executing the body of the Google sheet script for a sheet linked to the Google Form. How can this be done?'
The wait on the Google Form script completion is needed as shown in the code below. I cannot use the Utilities.sleep as it may lead to a race condition depending on the timing of the execution.
//SCRIPT LINKED TO FORM
function onFormSubmit(e) {
//does something
return;
}
//SCRIPT LINKED TO SHEET
function onFormSubmit(e) {
// Need to wait here until SCRIPT LINKED TO FORM finishes execution
return;
}
You indicated that you have multiple forms linking to a common spreadsheet, and that each form has its own bound script. The issue you describe is that several scripts may possibly get triggered simultaneously and so can end up racing to update the same spreadsheet. The spreadsheet additionally has a bound script that runs on a form submit trigger as well.
Instead of trying to coordinate the timing of many different script projects, implement all the functionality in one script project that handles them all.
Use a standalone script project that gets invoked by a number of Forms installable on submit triggers — one trigger per form.
The one script should update both the form data, and also the spreadsheet. Use the Lock Service to ensure that at any given moment you are only running one instance of the code that writes data to the spreadsheet.
You can access the spreadsheet from the script that is bound to the form like this:
function onFormSubmit(e) {
const lock = LockService.getScriptLock();
lock.waitLock(60 * 1000);
const form = e.source;
// ...adjust form response data in the form's response store
const spreadsheet = SpreadsheetApp.openById(form.getDestinationId());
// ...adjust form response data in the spreadsheet
lock.releaseLock();
// ...send email
}
This code assumes that the update process always finishes within one minute.

Calling an API endpoint upon changes to any Google Sheets files

What I wish to achieve:
Whenever a cell is changed in any google sheet on my shared drive (by
any user on the domain) I want to call an API endpoint and include
information about which cell was edited.
My approach:
I believe Google App Scripts Add-on is what I need. Installed for all users on the domain.
I see there are "bound" scripts and standalone scripts. For standalone scripts I am not able to create any other triggers than timer and calender based triggers. Bound scripts seem to be permanently bound to a single sheet and won't impact other sheets in any way.
What am I missing?
I find a few end-to-end tutorials on blogs for making bound scripts, but nothing for generic cross-domain stuff.
You can achieve all this through a standalone script. Create a standalone script and follow these steps:
Step 1: Get spreadsheet ids
First you would have to get the id of the different Spreadsheets in your shared drive. You can do it in Google Apps Script itself if you use the Advanced Drive Service (see Reference below). To activate this service, go to Resources > Advanced Google services... in your script editor and enable Drive API.
Then, write a function that will return an array of the spreadsheet ids in the shared drive. You will have to call Drive.Files.list for that. It could be something along the following lines (please write your shared driveId in the corresponding line):
function getFileIds() {
var params = {
corpora: "drive",
driveId: "your-shared-drive-id", // Please change this accordingly
includeItemsFromAllDrives: true,
q: "mimeType = 'application/vnd.google-apps.spreadsheet'",
supportsAllDrives: true
}
var files = Drive.Files.list(params)["items"];
var ids = files.map(function(file) {
return file["id"];
})
return ids;
}
Step 2: Create triggers for each spreadsheet
Install an onEdit trigger programmatically for each of the spreadsheets (an edit trigger fires a function every time the corresponding spreadsheet is edited, so I assume this is the trigger you want). For this, the ids retrieved in step 1 will be used. It could be something similar to this:
function createTriggers(ids) {
ids.forEach(function(id) {
var ss = SpreadsheetApp.openById(id);
createTrigger(ss);
})
}
function createTrigger(ss) {
ScriptApp.newTrigger('sendDataOnEdit')
.forSpreadsheet(ss)
.onEdit()
.create();
}
The function createTriggers gets an array of ids as a parameter and, for each id, creates an onEdit trigger: everytime any of these spreadsheets is edited, the function sendDataOnEdit will run, and that's where you will want to call your API endpoint with information about the edited cell.
Step 3: Call API endpoint
The function sendDataOnEdit has to get data from the edited cell and send it somewhere.
function sendDataOnEdit(e) {
// Please fill this up accordingly
var range = e.range;
var value = range.getValue();
UrlFetchApp.fetch(url, params) // Please fill this up accordingly
}
First, it can get information about the cell that was edited via the event object, passed to the function as the parameter e (you can get its column, its row, its value, the sheet and the spreadsheet where it is located, etc.). For example, to retrieve the value of the cell you can do e.range.getValue(). Check the link I provide in reference to get more details on this.
Second, when you have correctly retrieved the data you want to send, you can use UrlFetchApp.fetch(url, params) to make a request to your URL. In the link I provide below, you can see the parameters you can specify here (e.g., HTTP method, payload, etc.).
Please bear in mind that you might need to grant some authorization to access the API endpoint, if this is not public. Check the OAuth reference I attach below.
(You have to edit this function accordingly to retrieve and send exactly what you want. What I wrote is an example).
Summing this up:
In order to create the triggers you should run createTriggers once (if you run it more times, it will start creating duplicates). Run for example, this function, that first gets the file ids via Drive API and then creates the corresponding triggers:
function main() {
var ids = getFileIds();
createTriggers(ids);
}
Also, it would be useful to have a function that will delete all the triggers. Run this in case you want to start from fresh and make sure you don't have duplicates:
function deleteTriggers() {
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach(function(trigger) {
ScriptApp.deleteTrigger(trigger);
})
}
Reference:
Advanced Drive Service
Drive.Files.list
onEdit trigger
Install trigger programmatically
onEdit event object
UrlFetchApp.fetch(url, params)
Connecting to external APIs
OAuth2 for Apps Script
ScriptApp.deleteTrigger(trigger)
I hope this is of any help.

google app script: on form response, check which tab of the sheet collects the responses

I am trying to automate some extra data generated to a form response using Google App Script; (in particular, a unique identifier).
I can trigger a script on a form response, but I have multiple forms connected to this sheet document, so I need to make sure that the correct form is triggering the response, and then I would like to write the values to the correct sheet of the document.
How do I check which form is being triggered, and how can I find the sheet that collects it's responses?
I assume you've already set up the function on the sheet to capture the "on submit" event. You can get the [sheet] object from the e (event) argument.
//make sure to set up the form submit trigger from
//your script menu: Edit -> Current Project's Triggers
function onSubmit(e) {
var range = e.range
var sheet = range.getSheet();
//then do something with [sheet] object
}
I'm pretty sure you can only connect one form to a sheet, so knowing which sheet got written to will tell you which form was submitted.
Check out more about event triggers in the documentation.

Is there a way to have a Form submit responses to a specific sheet in a spreadsheet?

I'm aware of the following line:
form.setDestination(FormApp.DestinationType.SPREADSHEET, ss1.getId());
but I'm trying to get the form submissions to appendRow/submit to a specific sheet within spreadsheet ss1. Any way to do that within google app scripts? The above just points to spreadsheet ss1 and creates a "Form Responses" sheet within that spreadsheet.
Thanks in advance for the guidance!
P.S. For a little more background, I'm trying to set up a form to send expense data to my budget google sheets file, which has a "current month.year" format for the target sheet name.
You can write a custom event handler function (in the Script Editor for the Form NOT the Spreadsheet's Script Editor) and bind it to the Form submit event via an installable trigger (see documentation).
Within the body of the function you can reference the spreadsheet and the target sheet where you want to pass the form's data. However, you have to know the Form Service APIs and how to extract and format the form's respondent data manually since you won't have the benefit of having a "linked" spreadsheet bound to the form.
So you could write an event handler function (from the Form's Script Editor) as follows:
var SPREADSHEET_ID = "[Your spreadsheet ID]",
SHEET_NAME = "[Your sheet name]";
function onFormSubmit(e) {
var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME),
form = FormApp.getActiveForm();
// Extract and format form data and inject it into the sheet
}
Once you have that function in place you create an installable trigger to bind it to the form submit event in the form's Script Editor via the Edit menu command in the tool bar. Here is a link to the steps you'll need to follow to create the trigger.
Hope that helps.

How does google know which onsubmit trigger to execute when a form is submitted if you have multiple forms sending responses to a single spreadsheet

I need clarification from someone.
I understand that multiple forms can send their responses to a single spreadsheet in the new Google Sheets.
So I created two Google forms and had them both send their responses to a new Google spreadsheet. I then created two scripts in the spreadsheet (not in the forms). Each script is set to trigger when a form is submitted. Here are the two simple scripts.
function form2() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1");
sheet.getRange(sheet.getLastRow(),3).setValue("This is for test 2");
}
function form1() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 2");
sheet.getRange(sheet.getLastRow(),3).setValue("This is for test 1");
}
Somehow, Google is running the correct script for the appropriate form! How can that be? That is, form1() is automatically triggered when someone submits form1 and form2() is automatically triggered when someone submits form2.
So my question is, "How does Google know which script to run when a form is submitted?".
Thanks. I want to send all my form data to one spreadsheet if possible, but I don't want to set something up that will be unreliable.
-jay
Thanks wchiquito for your answer. I think that I figured this out with your help. Here is what I think is going on. If someone can confirm or disprove my conclusions it would be a big help to me.
There are two ways of creating a form in Google Apps.
Create a Form Directly.
Create a spreadsheet first and then create the form by selecting "Form" and then "Create Form" from within the menu bar of the spreadsheet.
But no matter how you create your form, you will end up having two files. One will be for the form and one will be for the spreadsheet which receives the data from the form. If you put the script in the form and in a spreadsheet, both will run when the form is submitted. The difference is that any script in the form will be limited in permissions. That is, any script in the form will not be able to access the spreadsheet where-as the script in the spreadsheet will have all of the permissions that you have (as long as the trigger is set under your account).
Now...
The new Sheets in Google Drive now has the ability to receive responses from more than one form while the old sheets in Google Drive only had the ability to receive one form responses. This eventually lead to my confusion and the answer from wchiquito is correct. It appears that the receiving spreadsheet will run as many onsubmit scripts as you like, but whether it is one or more than one, they will all run on all form data being received by the spreadsheet. That is, when the receiving spreadsheet receives form data from any form, ALL onsubmit scripts within the spreadsheet will be executed.
Both scripts should be executed, perhaps by using literal strings, the change is not visible.
Try running with the following changes:
function form2(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 1');
sheet.getRange(sheet.getLastRow(), 3).setValue(e.values);
}
function form1(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 2');
sheet.getRange(sheet.getLastRow(), 3).setValue(e.values);
}