Form title from FormResponse - google-apps-script

I have created a bunch of forms programmatically from a spreadsheet and bound a response trigger to each. When a form is filled out I can't seem to figure out which form was completed.
I tried:
Event.source.getId()
But that returns the id of the spreadsheet. I want to get the title of the form.
Edit: Clarification
This is how I set up the trigger:
ScriptApp.newTrigger('responseTrigger').forForm(form)
.onFormSubmit()
.create();
And when I try to get the title:
function responseTrigger(e) {
var source = e.source;
var title = e.source.getTitle().
Logger.log(title);
}
I get the following error:
Execution failed: TypeError: Cannot find function getTitle in object Spreadsheet.

I have finally solved this issue.
There is a know Google Scripts issue that is causing my problem. You would expect e.source.getID() to give you the form's ID, however it gives you the spreadsheet's ID. I found a solution in this question. What you do is find the unique ID of the trigger and cross reference it with all of the project triggers as follows:
function getFileByTriggerId(triggerId){
var triggers = ScriptApp.getProjectTriggers();
for(var i =0; i<triggers.length; i++){
if(triggers[i].getUniqueId() == triggerId){
return triggers[i].getTriggerSourceId();
}
}
}
And then in your response trigger you write:
function responseTrigger(e) {
var realEvent = getFileByTriggerId(e.triggerUid);
var form = FormApp.openById(realEvent);
var title = form.getTitle();
}

How about this?
If you want to retrieve the title when the form was submitted, you can use following script.
var title = e.source.getTitle()
If you always want to retrieve the title, you can use following script.
var form = FormApp.openById( form ID );
var title = form.getTitle();

Related

Apps Script custom function working in script editor but not in Google Sheet custom function

I have built a simple custom function in Apps Script using URLFetchApp to get the follower count for TikTok accounts.
function tiktok_fans() {
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var handle = '#charlidamelio';
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
var result = (match_text[2]);
Logger.log(result)
return result
}
The Log comes back with the correct number for followers.
However, when I change the code to;
function tiktok_fans(handle) {
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
//var handle = '#charlidamelio';
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
var result = (match_text[2]);
Logger.log(result)
return result
}
and use it in a spreadsheet for example =tiktok_fans(A1), where A1 has #charlidamelio I get an #ERROR response in the cell
TypeError: Cannot read property '2' of null (line 6).
Why does it work in the logs but not in the spreadsheet?
--additional info--
Still getting the same error after testing #Tanaike answer below, "TypeError: Cannot read property '2' of null (line 6)."
Have mapped out manually to see the error, each time the below runs, a different log returns "null". I believe this is to do with the ContentText size/in the cache. I have tried utilising Utilities.sleep() in between functions with no luck, I still get null's.
code
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
//tiktok urls
var qld = UrlFetchApp.fetch('https://www.tiktok.com/#thisisqueensland?lang=en').getContentText();
var nsw = UrlFetchApp.fetch('https://www.tiktok.com/#visitnsw?lang=en').getContentText();
var syd = UrlFetchApp.fetch('https://www.tiktok.com/#sydney?lang=en').getContentText();
var tas = UrlFetchApp.fetch('https://www.tiktok.com/#tasmania?lang=en').getContentText();
var nt = UrlFetchApp.fetch('https://www.tiktok.com/#ntaustralia?lang=en').getContentText();
var nz = UrlFetchApp.fetch('https://www.tiktok.com/#purenz?lang=en').getContentText();
var aus = UrlFetchApp.fetch('https://www.tiktok.com/#australia?lang=en').getContentText();
var vic = UrlFetchApp.fetch('https://www.tiktok.com/#visitmelbourne?lang=en').getContentText();
//find folowers with regex
var match_qld = raw_data.exec(qld);
var match_nsw = raw_data.exec(nsw);
var match_syd = raw_data.exec(syd);
var match_tas = raw_data.exec(tas);
var match_nt = raw_data.exec(nt);
var match_nz = raw_data.exec(nz);
var match_aus = raw_data.exec(aus);
var match_vic = raw_data.exec(vic);
Logger.log(match_qld);
Logger.log(match_nsw);
Logger.log(match_syd);
Logger.log(match_tas);
Logger.log(match_nt);
Logger.log(match_nz);
Logger.log(match_aus);
Logger.log(match_vic);
Issue:
From your situation, I remembered that the request of UrlFetchApp with the custom function is different from the request of UrlFetchApp with the script editor. So I thought that the reason for your issue might be related to this thread. https://stackoverflow.com/a/63024816 In your situation, your situation seems to be the opposite of this thread. But, it is considered that this issue is due to the specification of the site.
In order to check this difference, I checked the file size of the retrieved HTML data.
The file size of HTML data retrieved by UrlFetchApp executing with the script editor is 518k bytes.
The file size of HTML data retrieved by UrlFetchApp executing with the custom function is 9k bytes.
It seems that the request of UrlFetchApp executing with the custom function is the same as that of UrlFetchApp executing withWeb Apps. The data of 9k bytes are retrieved by using this.
From the above result, it is found that the retrieved HTML is different between the script editor and the custom function. Namely, the HTML data retrieved by the custom function doesn't include the regex of ("followerCount":)([0-9]+). By this, such an error occurs. I thought that this might be the reason for your issue.
Workaround:
When I tested your situation with Web Apps and triggers, the same issue occurs. By this, in the current stage, I thought that the method for automatically executing the script might not be able to be used. So, as a workaround, how about using a button and the custom menu? When the script is run by the button and the custom menu, the script works. It seems that this method is the same as that of the script editor.
The sample script is as follows.
Sample script:
Before you run the script, please set range. For example, please assign this function to a button on Spreadsheet. When you click the button, the script is run. In this sample, it supposes that the values like #charlidamelio are put to the column "A".
function sample() {
var range = "A2:A10"; // Please set the range of "handle".
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var sheet = SpreadsheetApp.getActiveSheet();
var r = sheet.getRange(range);
var values = r.getValues();
var res = values.map(([handle]) => {
if (handle != "") {
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
return [match_text[2]];
}
return [""];
});
r.offset(0, 1).setValues(res);
}
When this script is run, the values are retrieved from the URL and put to the column "B".
Note:
This is a simple script. So please modify it for your actual situation.
Reference:
Related thread.
UrlFetchApp request fails in Menu Functions but not in Custom Functions (connecting to external REST API)
Added:
About the following additional question,
whilst this works for 1 TikTok handle, when trying to run a list of multiple it fails each time, with the error TypeError: Cannot read property '2' of null. After doing some investigating and manually mapping out 8 handles, I can see that each time it runs, it returns "null" for one or more of the web_content variables. Is there a way to slow the script down/run each UrlFetchApp one at a time to ensure each returns content?
i've tried this and still getting an error. Have tried up to 10000ms. I've added some more detail to the original question, hope this makes sense as to the error. It is always in a different log that I get nulls, hence why I think it's a timing or cache issue.
In this case, how about the following sample script?
Sample script:
In this sample script, when the value cannot be retrieved from the URL, the value is tried to retrieve again as the retry. This sample script uses the 2 times as the retry. So when the value cannot be retrieved by 2 retries, the empty value is returned.
function sample() {
var range = "A2:A10"; // Please set the range of "handle".
var raw_data = new RegExp(/("followerCount":)([0-9]+)/g);
var sheet = SpreadsheetApp.getActiveSheet();
var r = sheet.getRange(range);
var values = r.getValues();
var res = values.map(([handle]) => {
if (handle != "") {
var web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
var match_text = raw_data.exec(web_content);
if (!match_text || match_text.length != 3) {
var retry = 2; // Number of retry.
for (var i = 0; i < retry; i++) {
Utilities.sleep(3000);
web_content = UrlFetchApp.fetch('https://www.tiktok.com/'+ handle + '?lang=en').getContentText();
match_text = raw_data.exec(web_content);
if (match_text || match_text.length == 3) break;
}
}
return [match_text && match_text.length == 3 ? match_text[2] : ""];
}
return [""];
});
r.offset(0, 1).setValues(res);
}
Please adjust the value of retry and Utilities.sleep(3000).
This works for me as a Custom Function:
function MYFUNK(n=2) {
const url = 'my website url'
const re = new RegExp(`<p id="un${n}.*\/p>`,'g')
const r = UrlFetchApp.fetch(url).getContentText();
const v = r.match(re);
Logger.log(v);
return v;
}
I used my own website and I have several paragraphs with ids from un1 to un7 and I'm taking the value of A1 for the only parameter. It returns the correct string each time I change it.

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);
}

Copying google apps script with forms

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).

Google Apps Script: create a pdf by editing response to google form

I'm hoping that someone can help me, What i want to do is to make a Google form create a new document and send that pdf via email when the form is edited.
I understand that when you edit a form the script inside a spreadsheet does not run, So i have put the script into a form and that is where i am having problems. I can not get the script to read the values that are been resubmitted on the form, it just keeps on coming up as "undefined" on the return
This is the script that i have got so far.
function Test(e) {
var form = FormApp.openById('****Form Key****'); //Enter Form ID here
var docTemplate = "***doc to be used***";
var docName = "***Name of document***";
var formResponse = form.getResponses();
var one = e.response; //I have tried e.value but does not pull through
var two = e.response; // Column 2
var copyId = DocsList.getFileById(docTemplate)
.makeCopy(docName)
.getId();
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document’s body section
var copyBody = copyDoc.getActiveSection();
copyBody.replaceText('one', one);
copyBody.replaceText('two', two);
// Save and close the temporary document
copyDoc.saveAndClose();
// Convert temporary document to PDF by using the getAs blob conversion
var pdf = DocsList.getFileById(copyId).getAs("application/pdf");
//Sends the email
var email_Address = "***Email Address***"
var subject = "***Subject";
var body = "**Body***";
MailApp.sendEmail(email_Address, subject, body, {attachments: pdf});
}}
Any help will be greatly appreciated as i have been stuck on this for a while. thank you.
In a Form Response Trigger function, the event is an instance of Class FormResponse. Because of that, you don't need to open the form, or get responses... you've just had one handed to you, which you access via e.response. (See Form Response Events in Understanding Events.)
// Handle form response event
// This function must be a Form Response Trigger, attached to
// a Form (not a Spreadsheet).
function rightTest( e ) {
var formResponses = e.response.getItemResponses(); // array of responses
var one = formResponses[0];
var two = formResponses[1];
...
}
As of April 2015, DocsList is not supported, simply replace DocsList. with DriveApp. How to update DocsList to DriveApp in my code