Accessing user entered data upon submit in google forms - google-apps-script

I have a google-form that has the following two fields:
Email address: - A text box
Tool: - A radio button
Tool 1
Tool 2
Tool 3
The user would enter his email address and select a tool and click submit. I would like the following message to appear:
Thanks for responding. An email has been sent to you to at entered email address to download selected tool.
I have the following piece of code in the script editor
function emailFormSubmission() {
var form = FormApp.getActiveForm();//the current form
var dest_id = form.getDestinationId(); //the destination spreadsheet where form responses are stored
var ss = SpreadsheetApp.openById(dest_id);//open that spreadsheet
var theFormSheet = ss.getSheets()[0]; //read the first sheet in that spreadsheet
var row = theFormSheet.getLastRow(); //get the last row
var emailid = theFormSheet.getRange(row,2,1,1).getValue();//get column 2 corresponding to the email id. column 1 is timestamp. so, skip that.
var tool = theFormSheet.getRange(row,3,1,1).getValue();//get column 3 corresponding to the selected tool.
form.setConfirmationMessage('Thanks for responding. An email has been sent to you '+ emailid + ' to download' + tool);
}
I have also set the triggers to be Run -> emailFormSubmission, Events -> from Form , onFormSubmit.
What happens is: Suppose the first user ('A') enters his information and clicks submit. His entered information gets displayed correctly. When second user ('B') enters his information and clicks submit, A's information is displayed. When third user ('C') enters his information and clicks submit, then B's information is displayed. I found that the issue is with "getlastrow()" since the spreadsheet is updated after emailFormSubmission is processed.
Whats wrong with the above code? How do I fix this?
UPDATE
Based on #wchiquito's comments, I changed the code to following to make it work.
function emailFormSubmission(e) {
var form = FormApp.getActiveForm();
//Check this link on how to access form response:
//https://developers.google.com/apps-script/understanding_events?hl=en
var responses = e.response;//e is of type formresponse.
var emailid = responses.getItemResponses()[0].getResponse();
var tool = responses.getItemResponses()[1].getResponse();
Logger.log(emailid);
Logger.log(tool);
form.setConfirmationMessage('Thanks for responding. An email has been sent to '+ emailid + ' with instructions to download ' + tool +'. If you do not find our email in your inbox, please check your spam folder');
Logger.log(form.getConfirmationMessage());
}

Remember that the event On form submit (Understanding Events) receives a parameter that has the following structure:
values​​
range
namedValues​​
and you can do something like:
function emailFormSubmission(e) {
...
var row = e.range.getRow();
...
}
Try the following code to observe the structure of the parameter e:
function emailFormSubmission(e) {
...
Logger.log(e);
...
}
UPDATE
First, excuse my confusion, I showed you the structure of a Spreadsheet form submit event when you really are using a Form submit event.
Sure enough, a Form submit event has the following structure:
response
Returning an object of type FormResponse.
Therefore, defining the event: On submit form (Form submit event), you can do something like the following:
function emailFormSubmission(e) {
var itemResponses = e.response.getItemResponses();
for (var i = 0, len = itemResponses.length; i < len; ++i) {
Logger.log('Response #%s to the question "%s" was "%s"',
(i + 1).toString(),
itemResponses[i].getItem().getTitle(),
itemResponses[i].getResponse());
}
}
However, the confirmation message set according to the data sent as responses of the form, does not seem very clear, you can set the message, but will not display for the active response, if not for the next.

My first guess is these two lines right here:
var emailid = theFormSheet.getRange(row,2,1,1).getValue();//get column 2 corresponding to the email id. column 1 is timestamp. so, skip that.
var tool = theFormSheet.getRange(row,3,1,1).getValue();//get column 3 corresponding to the selected tool.
When you call getLastRow() on a sheet - you're getting the last row. Sure, but considering the order of events and how these values are processed, you need a +1, to get the most recent submission. Currently you're one row behind when your code runs to update the Form confirmation message.
So just change your code to the following:
var emailid = theFormSheet.getRange(row+1,2,1,1).getValue();
var tool = theFormSheet.getRange(row+1,3,1,1).getValue();
Spreadsheets are the most confusing of Google services, in my opinion. When you get values in the Spreadsheet, they're returned as an [] or [][] depending on what your Range is when you call getValues(). But getRange() on a sheet starts at index 1 (to make it easier to read in code I suppose). Often times I find that I have an off-by-one error because of the way data is passed around. Just keep that in mind as you work with Spreadsheets :)

Short answer: want you want can't be done with Google forms.
Explanation:
form.setConfirmationMessage() sets the confirmation message for the form as stored on the server, not for the current active form. Same applies for example for form.setTitle(). The active form will not be modified. One would expect different behaviour for the confirmation message, but alas, this is not the case.

Yes, you can do this with the add-on "Formfacade".
It's free to use in 1 form.

Related

Sending emails twice with MailApp.sendEmail

I have created a google apps script to automate emails once a Google Form is submitted. The script is quite simple:
function AutoConfirmation(e){
var theName = e.values[1];
var theEmail = e.values[2];
var theSubject= e.values[3];
var myEmail = "myemail#gmail.com";
var theMessage = e.values[4];
var subject = "Contact form response – " + theSubject;
var message = theMessage;
MailApp.sendEmail (myEmail, subject,message);
}
However, fo some reason I can't figure out, every time a form is submitted I get two instant emails:
Has the data submitted (all works as expected)
Is empty (e.g. subject is "Contact form response –")
I even started from scratch in a different Google account I have and the same issue happens.
Appreciate any suggestions!
It appears the issue is being caused by the internal process that syncs form responses to the spreadsheet. Under some circumstances it makes slight updates to the "Timestamp" column of previously submitted form responses, which cause the onFormSubmit triggers to fire again for those rows (albeit with incomplete event objects).
The engineering team is still working on a fix, but in the mean time you can work around this issue by filtering out form submit events that only affect the timestamp column. Since you can reorder the columns in a Form Responses sheet, the best way would be to check if the event's range only covers a single column:
function onFormSubmit() {
if (e.range.columnStart == e.range.columnEnd) return;
// The rest of your code
// ...
}

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 prevent Google Forms from converting form input to scientific notation format

I have a simple script set up that sends emails based on Google Form entries using a script-based VLookup to get the contact emails. In some cases, Google Forms converts longer numbers entered into the form field to scientific notation. A workaround I have been using is to enter an apostrophe before the number - for some reason this keeps the cell formatted to plaintext. I would like to find a solution that does not require this extra step.
The sheet has a form with a single field, eGCs. The eGCs field can contain ANY combination of letters and numbers and may be a multi-line string. The script sends an email to the user onFormSubmit with the eGCs field entry in the body of the email. The problem arises when I try to submit a very long string that is only numbers and the form entry variable is converted to scientific notation.
I need whatever the user enters in the eGCs field to appear EXACTLY as they entered it on both the Responses 1 sheet and in the body of the email that is sent. Here is the code:
function onFormSubmit(e) {
var eGCs = e.values[1];
var email = Session.getActiveUser().getEmail();
//Replace the Google Sheets formatted line breaks with HTML line breaks so they display properly in the email:
eGCs = eGCs.replace(/\n/g, '<br>');
//Send the email:
var subject = "This is only a test";
var body = eGCs;
MailApp.sendEmail(email, subject, body, {htmlBody: body})
return
}
If I submit
6110523527643880
...into the form, the number is changed to scientific notation format and appears as 6.11052E+15 both on the sheet and in the email that is sent. If I submit a multi-line string such as:
6110523527643880
6110523527643880
6110523527643880
...then the script works fine and the form field entry is not converted (probably because Google does not consider it a number any more). I need it to appear exactly as entered whether or not the form entry is a single line or multiple lines.
Here is my example sheet / script / form. It should be public, so please feel free to test it.
Form responses in Forms (as opposed to Spreadsheets) store responses as Strings. Your trigger function could grab the response from the form to get the string as entered by the respondent.
function onFormSubmit(e) {
// Get response sheet. First version works only in contained script,
// second works even in stand-alone scripts.
// var sheet = SpreadsheetApp.getActiveSheet();
var sheet = e.range.getSheet();
// Get URL of associated form & open it
var formUrl = sheet.getParent().getFormUrl();
var form = FormApp.openByUrl(formUrl);
// Get response matching the timestamp in this event
var timestamp = new Date(e.namedValues.Timestamp);
// NOTE: There is a race condition between the updates in Forms and Sheets.
// Sometimes (often!) the Spreadsheet Form Submission trigger function is invoked
// before the Forms database has completed persisting the new Responses. As
// a result, we might get no results when asking for the most recent response.
// To work around that, we will wait and try again.
var timeToGiveUp = 0;
do {
if (timeToGiveUp > 0) Utilities.sleep(1000); // sleep 1s on subsequent tries
timeToGiveUp++;
var responses = form.getResponses(timestamp);
} while (responses.length == 0 && (timeToGiveUp < 3));
Logger.log("time to give up "+timeToGiveUp);
var response = responses[0]; // assume just one response matches timestamp
var itemResponses = response.getItemResponses();
var eGCsItemNumber = 1; // Indicates where the question appears in the form
var eGCs = itemResponses[eGCsItemNumber-1].getResponse().toString();
// You now have exactly what the respondent typed, as a string.
// It can be used as-is in an email, for example.
var body = "The user entered: "+eGCs;
MailApp.sendEmail(
Session.getActiveUser().getEmail(),
"This is only a test",
body
);
// To preserve the value in the spreadsheet, we must
// force it to remain a string by prepending a tick (')
var eGCsCol = 2;
e.range.offset(0,eGCsCol-1,1,1).setValue("'"+eGCs);
}
Note wrt Race condition comment: The actual work that was needed in this area of the code was a single line:
var responses = form.getResponses(timestamp);
While playing with this, I found that I was frequently receiving an exception, the same as noted in comments below this answer...
Cannot find method getResponses(object)
It turned out that this only happened when the function was triggered by a form submission event, not when running from the editor/debugger with simulated events. That implies that, for a short period of time, the response we're trying to handle is not returned by the call to getResponses().
Because of the way that shared documents are implemented, there is a propagation delay for any change... that's the time it takes for a change in one view of an asset to propagate to all other views.
In this situation, our trigger function has launched with a spreadsheet event, and then opens a view of the Form and tries to read the newest responses before that view contains them.
A simple work-around would be to sleep() for a period of time that would allow propagation to complete.
Utilities.sleep(5000); // 5s pause
var responses = form.getResponses(timestamp);
Simple, yes - but inefficient, because we'd be waiting even when we didn't need to. A second problem would be determining how long was long enough... and what if that changed tomorrow?
The chosen work-around will retry getting responses only if it is not successful the first time. It will only wait when doing a retry. And it won't wait forever - there's a limiting condition applied, via timeToGiveUp. (We could have added an additional check for success after the loop, but since the next statement will through an exception if we've blown through our time limit, we can let it do the dirty work.)
var timeToGiveUp = 0;
do {
if (timeToGiveUp > 0) Utilities.sleep(1000); // sleep 1s on subsequent tries
timeToGiveUp++;
var responses = form.getResponses(timestamp);
} while (responses.length == 0 && (timeToGiveUp < 3));
Lots more than one line of code, but more robust.
I am assuming eGCs is the response with the number.
e.values[2] will always come back as a string (in this case "6.15312E+16"), therefore you cannot convert that to the original number as you loose everything after that last 2. Even if you convert it, the best you can get is "61531200000000000"
Instead, you can pull the value from the spreadsheet.
In the beginning of your onFormSubmit() function add this code:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Form Responses 1");
var eGCs = sheet.getRange(sheet.getLastRow(), 2, 1, 1).getValue();
try {
eGCs = eGCs.toFixed();
} catch (e) {
Logger.log(eGCs);
}
Your eGCs will now return as the full number if it's a number, otherwise it will return as text.
If you want the spreadsheet to have the right format when new responses are submitted add this to your code:
sheet.getRange(sheet.getLastRow(), 2, 1, 1).setNumberFormat("000");
This will convert the new row added by the form in to the correct format. This does not affect the actual value in the spreadsheet, only the way it is formatted.

Getting active spreadsheet row after form submission

I am relatively new to Google Apps Script and have been using a very simple script that I created about a year ago that is triggered when a user submits inputs via a Google Form. The script has been working perfectly until approximately this past May and I've been scrambling trying to figure out what happened when I haven't made any changes to my code. I have searched many different places and cannot seem to find what is wrong.
Basically, I have a form that users complete and then submit. Upon submission, the script takes the inputs from the most recent row and stores them in variables that would then be assembled into an email message confirmation that acknowledges each user's submitted input.
Here is my code:
function acknowledgement() {
var ActiveSheet = SpreadsheetApp.getActiveSheet();
var ActiveRow = ActiveSheet.getActiveRange().getRow();
var emailAddy = ActiveSheet.getRange("E"+ActiveRow).getValue();
var locvar = ActiveSheet.getRange("C"+ActiveRow).getValue();
var employeevar = ActiveSheet.getRange("B"+ActiveRow).getValue();
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Variables");
var contactvar = sh.getRange("A2").getValue();
var subject = sh.getRange("B2").getValue();
var contactvar2 = sh.getRange("C2").getValue();
var linebrk = "<br />";
var msg = "Dear " + employeevar + ","
+ linebrk + linebrk
+ "This confirms that you have completed your review of the latest security presentation."
+ linebrk + linebrk
+ "Your location number is " + locvar + "."
+ "Thank you very much for your participation."
+ linebrk + linebrk
+ contactvar;
var msghtml = '<p>'+msg+'</p>';
var advancedargs = {cc:contactvar2, htmlBody:msghtml};
MailApp.sendEmail(emailAddy, subject, msg, advancedargs);
}
What is currently happening is that my code is no longer grabbing the current row number (i.e. the active row that was just submitted by a user). Instead, it is simply grabbing the top row of the sheet (i.e. my row headings like 'Employee Name', 'Email Address', etc.) and assigning those row headings to the variables thus producing an error when trying to send the email confirmation. For instance my variable emailAddy would contain "Email Address" causing sendEmail to fail.
Any feedback would be appreciated!
Using getActiveRow in the context of a form submission is somewhat strange as one cannot consider that a user is actually active on the sheet... I don't know why you did choose that approach and I'm actually wondering how it happened to work for so long...
There are other possibilities to handle form submissions but the one that will need the fewest changes in your code is to simply use getLastRow() instead of getActiveRange().getRow()
There are a few risks to use that simple "strategy" as there might be concurrency issues when 2 or more people send a form simultaneously.
The other solution is to get all the field values directly from the event properties that comes on form submission as in that case each event is unique, no matter how it comes into the spreadsheet but your script will have to be rewritten more deeply.
I believe that Google Forms has well covered the case you mention, the trigger "onFormSubmit" the spreadsheet, receives an object as a parameter with all the information you need.
I agree with Serge that the script be rewritten deeply, but definitely, it will save many problems.
Go to the documentation, specifically in "Spreadsheet Form Submit Events" https://developers.google.com/apps-script/understanding_events.
You are looking for the code e.range.getRow().
So we have:
function onFormSubmit(e) {
const row = e.range.getRow()
}
You'll also need to setup a trigger to the onFormSubmit function.
Edit > Current project's triggers
Now (finally) some fun: everytime a form is submitted, you'll know the row it corresponds to. Have fun!
Note that the onFormSubmit function could have any name - the trigger will map to any named function and pass to it an "e" argument that contains range.getRow() among other information.

addEditors to Document from formSubmit

I'm Working on creating script that will perform the actions described below. so far, I've managed to get the first two parts to function, but am now stuck on getting anything more to work. I've reviewed several response forums and tried the suggestions, but no success.
Desired script flow:
form submitted from spreadsheet form
completes fields:
Timestamp
Username (email collected on submission)
Student
Grade
Intervention Plan
Core Reading/Math
Team (email list)
1 script runs onFormSubmit that then creates a copy of a template document, renames that new copy to the e.value "student" submitted in form,
2 then replaces selected text strings within the document with values submitted in the form.
3 Add editors to new document and sends notification with desired instructions
4 creates event (one week from submission date) event details include instructions and link to shared document for team members to complete with their input, sends event invitation to email list.
Here is the working script so far.
function formSubmitValues(e) {
var timeStamp = e.values[0];
var userEmail = e.values[1];
var student = e.values[2];
var grade = e.values[3];
var conern = e.values[4];
var readingCore = e.values[5];
var mathCore = e.values[6];
var interventions = e.values[7];
var team = e.values[8].toString(); // "just to be sure"..Henrique says add .toString this allowed the replaceText part to work
//Makes copy of template document and renames
var tempID = ("1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE") // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(student + " Initial Referral") // names new copy as student's name
.getId();
// trying to add editors to new document using email list generated in form submit value of "team"
DocsList.getFileById(copyId).addEditors([team]);
// replaces text within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%STUDENT%", student);
body.replaceText("%DATE%", timeStamp);
body.replaceText("%TEACHER%", userEmail);
body.replaceText("%TEAM%", team);
return doc;
}
REPORTED ISSUE RESPONSE: Here is what they said: "The function takes an array or strings, like: DocsList.getFileById(copyId).addEditors(['parent#domain.com', 'parent2#domain.com']);
I tried entering emails directly into script like this and things worked.
So my problem is not the 'addEditors method, but lies in getting the form submitted emails to be passed correctly. Any suggestions on how I would do this?
I have tried what I believe to be all combinations of using .toString(), or not, and using .Split(',').
RE-DEFINE PROBLEM : So it is an issue of how the emails are passed from the e.values form submit.
Here is where I'm at:
When I type emails into script directly: .addEditors(['parent#domain.com', 'email2#domain.net', 'email3#gmail.com']) it works, (I did have to move the addEditors method in the script to go right after the .makeCopy instead of at the end.)
This is what the Execution Transcript shows:
File.addEditors([[parent#domain.com, email2#domain.net]]) and it runs the rest of the script. note: One part I don't understand is the single quotes i.e. 'email' They must be typed in the script, but don't show up on Transcript when run. I've tried putting them around emails in the form, but it makes them show in Transcript and still doesn't run anyway.
So this is what script looks like now:
var tempID = ("1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE") // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(student + " - TestingCopy") // names new copy as student's name + text
.addEditors([team.split(',')])
.getId();
But when I use the var team with or without .split(',') it does not work. But in the Transcript it shows:
File.addEditors([[rreynolds#domain.net, parent#domain.com]])
which looks identical as to what shows when it does work, but that is the last thing shown in Transcript and editors are not added to document and the script does not finish.
I'm obviously not understanding something here. Is there a way I could get the emails in the team e.values to be treated in a way that the addEditors method is requiring? In the spreadsheet cell they appear as a CSV. i.e rreynolds#domain.net, parent#domain.com
Do they have to be read one at a time, or something?
I'm learning a lot, and appreciate all your help. I am sorry for the confusion with all the comments, but am not sure of the correct way to address issues in this forum. For example: should I go back and edit my original script to show the current version, or add it someplace else? I'm trying to keep the conversation flowing, so that it is easier for others to follow - Thanks rob
please let me give a last (hopefully) clear answer : (thanks for sharing the spreadsheet, this is far more easy to work on ;-)
here is your code fully working.
I have created some intermediate variables to show how it works.
function formSubmitEditors(e) {
// defines spreadsheet form events on submit of form. This function formSubmitEditors is triggered on formSubmit
var timeStamp = e.values[0];
var fileName = e.values[1];
var team = e.values[2].replace(/, /g,"|"); // remove unwanted spaces and commas replace by | for visibility ;-)
Logger.log(team);// contains | as separators
var teamArray = team.split('|');
Logger.log(teamArray.length+' : '+teamArray);// check that it is an array of x elements
//Makes copy of template document and renames
var tempID = '1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE' // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(fileName + " - TestingCopy") // names new copy as student's name + text
.getId(); //
var file = DocsList.getFileById(copyId).addEditors(teamArray);
// replaces merged-text values within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%FILE%", fileName);// you wrote %FILENAME% in place of %FILE%
body.replaceText("%DATE%", timeStamp);
body.replaceText("%TEAM%", team);// it will be shown with | as separators, if you don't like it replace team by teamArray.toString() to get commas again.
}
EDIT : I removed the toString() for team event, not necessary since e.parameters are already strings.
EDIT2 : to be complete and do what you needed in the initial question you could replace the end of the code with this one that creates the Cal event on next week and sends invites with link to the doc.
var file = DocsList.getFileById(copyId).addEditors(editorsArray);
var fileurl = file.getUrl();
Logger.log(fileurl)
// replaces merged-text values within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%FILE%", fileName);
body.replaceText("%DATE%", timeStamp);
body.replaceText("%MAILS%", editors);
var Cal = CalendarApp.getCalendarsByName('testencodage')[0];// replace with your calendar name you want to use
var newEvent = Cal.createAllDayEvent('Fill the questionnary', new Date(new Date(newtimeStamp).getTime()+7*24*3600*1000), { guests : e.values[2] , sendInvites : true , description :"Don't forget to fill this document "+fileurl})
}
I'd suggest a couple of things
Add a few Logger.log statements in your code to print out debug information.
Add a try ... catch block around the entire section of code and print out the exception. See if you are getting any exception.
Last, use the Execution transcript window to see where your script stopped, if it did.
There are 2 ways to add an editor by email : addEditors([emailAddresses]) and addEditor(emailAddress)
The first has an "s" and needs an array of email adress strings, the second takes a single string as argument. You should use the second or add [brackets] to the email string in your code.
concerning you comment // need to figure out where/how to: .addEditors(email1,email2,etc);
// is this done from DocsList or DocumentApp class?
addEditor() and addEditors() belong to the file class, a member of DocsList class , you can add user(s) using user(s) objects or user(s) email(s) as explained in the doc.
It could be used like this DocFile.addEditors([email1,email2])
EDIT : A lot of comments on this post, sorry about that, it has become quite uneasy to read... I tested these addEditors feature with spreadsheet and it works as expected, using simple array for multiple user emails and string for single email. The document service seems to have a problem with the addEditor() method and it should be reported to the issue tracker.
REPORTED I've reported issue #1512 - Rocketrob