I am trying (and not succeeding at the moment) to populate a Google Form from a Google Spreadsheet using the items that I have picked up from this website and this extremely useful answer provided by Mogsdad here.
Ideally, I'm looking for:
The Logger.log (URL) logs URL's for all the data stored in the spreadsheet, is it possible to just log the last entry and use this to generate the URL?
Is it then possible for the pre-filled URL to auto submit once populated with the data? I have found this useful article here which suggests that this can be done?
The data that is stored in the Google Spreadsheet is data captured from another Google Form. This is due to the need of using Excel (lack of Internet connectivity) with a concatenate formula to merge all cells with data into one. This is then submitted on the other Google Form which has this script to split the data out by column ready to answer the questions with. Will this impact the trigger needed to auto submit when a submission is made?
May I add that I have a rather limited understanding on this so please go easy if this seems rather easy to do!
is it possible to just log the last entry and use this to generate the URL?
I'm not sure I follow you here - in that other answer, the Logger.log() statement was just to demonstrate that you could generate the correct URL, if you wanted to distribute it. Instead of logging it, you'd just use the content of the variable url.
But let's move on, because I think this is a little off your path.
Is it then possible for the pre-filled URL to auto submit once populated with the data?
There's a better starting point. Can we programmatically submit information from a spreadsheet into a google form? Sure! See Use App Scripts to open form and make a selection. It's a more reliable way to do the job than what you see in the "URL Tricks" post.
In the case of "auto submit", or simulating a form submission, you don't need to worry about the pre-filled URL at all. That's a shortcut for pesky humans. What you want is to put together the payload for a POST request, instead, and have a computer bypass the form UI altogether.
Something about Excel... Will this impact the trigger needed to auto submit when a submission is made?
(Sounds like...) You are using Form1 to get data into Spreadsheet1, then expecting to react to the (human?) submission of Form1 by having the machine submit Form2 after breaking apart the data from Form1.
Yeah, you'll need to be careful that the column split is done before you try to read the information to submit Form2.
I suggest that you would be best served with a form-submission trigger function for Spreadsheet1 that splits the string received from Form1 then immediately sends the POST to Form2. I'd then record the fact that this action has occurred, using the technique from Spreadsheet Email Trigger.
Is it then possible for the pre-filled URL to auto submit once populated with the data? I have found this useful article here which suggests that this can be done?
As Mogsdad said
There's a better starting point. Can we programmatically submit information from a spreadsheet into a google form? Sure!
According to an edit to Use App Scripts to open form and make a selection, the payload/post method is now obsolete. The alternative is to use the Form App service. Below is an adaptation that I made to evenBetterBuilURLs1 by Mogsdad to submit responses instead of create pre-filled URLs and to be used in a stand-alone Google Script Project File.
The original code lines that were changed are commented out. Also I inserted some breaklines to avoid the horizontal scroll bar.
/**
* Use Form API to populate form
*
* Addapted from https://stackoverflow.com/a/26395487/1677912
*/
function populateFormResponses() {
//var ss = SpreadsheetApp.getActive();
var id = '11KDxp1C6jAZaTMNlGHke8zEzQ7aZrFSFGABdwUHEV80';
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheetByName("Form Responses 1");
var data = ss.getDataRange().getValues(); // Data for pre-fill
var headers = data[0]; // Sheet headers == form titles (questions)
var formUrl = ss.getFormUrl(); // Use form attached to sheet
var form = FormApp.openByUrl(formUrl);
var items = form.getItems();
//var urlCol = headers.indexOf("Prefilled URL"); // If there is a column labeled this
// way, we'll update it
// Skip headers, then build URLs for each row in Sheet1.
for (var row = 1; row < data.length; row++ ) {
//Logger.log("Generating pre-filled URL from spreadsheet for row="+row);
Logger.log("Generating response from spreadsheet for row="+row);
// build a response from spreadsheet info.
var response = form.createResponse();
for (var i=0; i<items.length; i++) {
var ques = items[i].getTitle(); // Get text of question for item
var quesCol = headers.indexOf(ques); // Get col index that contains this
// question
var resp = ques ? data[row][quesCol] : "";
var type = items[i].getType().toString();
Logger.log("Question='"+ques+"', resp='"+resp+"' type:"+type);
// Need to treat every type of answer as its specific type.
switch (items[i].getType()) {
case FormApp.ItemType.TEXT:
var item = items[i].asTextItem();
break;
case FormApp.ItemType.PARAGRAPH_TEXT:
item = items[i].asParagraphTextItem();
break;
case FormApp.ItemType.LIST:
item = items[i].asListItem();
break;
case FormApp.ItemType.MULTIPLE_CHOICE:
item = items[i].asMultipleChoiceItem();
break;
case FormApp.ItemType.CHECKBOX:
item = items[i].asCheckboxItem();
// In a form submission event, resp is an array, containing CSV strings. Join
// into 1 string.
// In spreadsheet, just CSV string. Convert to array of separate choices, ready
// for createResponse().
if (typeof resp !== 'string')
resp = resp.join(','); // Convert array to CSV
resp = resp.split(/ *, */); // Convert CSV to array
break;
case FormApp.ItemType.DATE:
item = items[i].asDateItem();
resp = new Date( resp );
break;
case FormApp.ItemType.DATETIME:
item = items[i].asDateTimeItem();
resp = new Date( resp );
break;
default:
item = null; // Not handling DURATION, GRID, IMAGE, PAGE_BREAK, SCALE,
// SECTION_HEADER, TIME
break;
}
// Add this answer to our pre-filled URL
if (item) {
var respItem = item.createResponse(resp);
response.withItemResponse(respItem);
}
// else if we have any other type of response, we'll skip it
else Logger.log("Skipping i="+i+", question="+ques+" type:"+type);
}
// Submit response
response.submit();
// Generate the pre-filled URL for this row
//var editResponseUrl = response.toPrefilledUrl();
// If there is a "Prefilled URL" column, update it
//if (urlCol >= 0) {
// var urlRange = sheet.getRange(row+1,urlCol+1).setValue(editResponseUrl);
//}
}
};
Related
I'm looking for a way to 'pre fill' a google form with specific data from a google spreadsheet. The form will have the same 'standard' questions for everyone, but the data in the first two question will be 'prefilled' with unique data from an existing google spreadsheet. The data will be unique based on their email address in the existing spreadsheet.
SOURCE SPREADSHEET EXAMPLE
Col 1 Col 2 Col 3
email name birthday
#mike Mike Jones May 9th 1975
#jim Jim Smith April 19th 1985
FORM EXAMPLE ONE
Question 1 - prefilled with data (Mike Jones) from a google spreadsheet.
Question 2 - prefilled with data (May 9th 1975) from a google spreadsheet.
Question 3 - blank (awaiting user response)
Question 4 - blank (awaiting user response)
FORM EXAMPLE TWO
Question 1 - prefilled with data (Jim Smith) from a google spreadsheet.
Question 2 - prefilled with data (April 19th 1985) from a google spreadsheet.
Question 3 - blank (awaiting user response)
Question 4 - blank (awaiting user response)
Does anyone know if this can be done? If yes, any help or direction will be GREATLY appreciated.
Thank you in advance!
Todd
You can create a pre-filled form URL from within the Form Editor, as described in the documentation for Drive Forms. You'll end up with a URL like this, for example:
https://docs.google.com/forms/d/--form-id--/viewform?entry.726721210=Mike+Jones&entry.787184751=1975-05-09&entry.1381372492&entry.960923899
buildUrls()
In this example, question 1, "Name", has an ID of 726721210, while question 2, "Birthday" is 787184751. Questions 3 and 4 are blank.
You could generate the pre-filled URL by adapting the one provided through the UI to be a template, like this:
function buildUrls() {
var template = "https://docs.google.com/forms/d/--form-id--/viewform?entry.726721210=##Name##&entry.787184751=##Birthday##&entry.1381372492&entry.960923899";
var ss = SpreadsheetApp.getActive().getSheetByName("Sheet1"); // Email, Name, Birthday
var data = ss.getDataRange().getValues();
// Skip headers, then build URLs for each row in Sheet1.
for (var i = 1; i < data.length; i++ ) {
var url = template.replace('##Name##',escape(data[i][1]))
.replace('##Birthday##',data[i][2].yyyymmdd()); // see yyyymmdd below
Logger.log(url); // You could do something more useful here.
}
};
This is effective enough - you could email the pre-filled URL to each person, and they'd have some questions already filled in.
betterBuildUrls()
Instead of creating our template using brute force, we can piece it together programmatically. This will have the advantage that we can re-use the code without needing to remember to change the template.
Each question in a form is an item. For this example, let's assume the form has only 4 questions, as you've described them. Item [0] is "Name", [1] is "Birthday", and so on.
We can create a form response, which we won't submit - instead, we'll partially complete the form, only to get the pre-filled form URL. Since the Forms API understands the data types of each item, we can avoid manipulating the string format of dates and other types, which simplifies our code somewhat.
(EDIT: There's a more general version of this in How to prefill Google form checkboxes?)
/**
* Use Form API to generate pre-filled form URLs
*/
function betterBuildUrls() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("Sheet1");
var data = ss.getDataRange().getValues(); // Data for pre-fill
var formUrl = ss.getFormUrl(); // Use form attached to sheet
var form = FormApp.openByUrl(formUrl);
var items = form.getItems();
// Skip headers, then build URLs for each row in Sheet1.
for (var i = 1; i < data.length; i++ ) {
// Create a form response object, and prefill it
var formResponse = form.createResponse();
// Prefill Name
var formItem = items[0].asTextItem();
var response = formItem.createResponse(data[i][1]);
formResponse.withItemResponse(response);
// Prefill Birthday
formItem = items[1].asDateItem();
response = formItem.createResponse(data[i][2]);
formResponse.withItemResponse(response);
// Get prefilled form URL
var url = formResponse.toPrefilledUrl();
Logger.log(url); // You could do something more useful here.
}
};
yymmdd Function
Any date item in the pre-filled form URL is expected to be in this format: yyyy-mm-dd. This helper function extends the Date object with a new method to handle the conversion.
When reading dates from a spreadsheet, you'll end up with a javascript Date object, as long as the format of the data is recognizable as a date. (Your example is not recognizable, so instead of May 9th 1975 you could use 5/9/1975.)
// From http://blog.justin.kelly.org.au/simple-javascript-function-to-format-the-date-as-yyyy-mm-dd/
Date.prototype.yyyymmdd = function() {
var yyyy = this.getFullYear().toString();
var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
var dd = this.getDate().toString();
return yyyy + '-' + (mm[1]?mm:"0"+mm[0]) + '-' + (dd[1]?dd:"0"+dd[0]);
};
I'm trying to figure out how to do conditional statements tilizing app script in Google form with my already existing form. I have an email field which supposedly checks from a google sheet values in one column (email column). If what has been inputted into the email field exists in the Google sheet column for email, an alert prompts that the email already exists, help text will also show with a message like "email already exist". If the Ok button of the alert prompt is clicked, user can now go back into the Google form and edit their answers into the email field, and if the email address inputted does not exist in the Google sheet, nothing happens, and the user can proceed answering the form and lastly submit the form.
I have tried textvalidation but it seems text validation would only be able to answer one part of what I wanted to do with my form - show a help text. Below is my working app script:
var sheet = SpreadsheetApp.openById("IdOfMyGoogleSheet");
function validationTest() {
var data = sheet.getRange("Form Responses 1!F2:F").getValues();
var form = FormApp.openById('formID');
var item = form.getItemById(itemID)asTextItem();;
var textValidation = FormApp.createTextValidation()
.setHelpText("Email already exist")
.requireTextContainsPattern(data)
.build();
item.setValidation(textValidation);
}
Thanks in advance!
The first this to note is that a pattern is a regular expression. You also need to use requireTextDoesNotMatchPattern instead of requireTextContainsPattern. Here is what I've tested:
function setPattern() {
// Get the firm item
const form = FormApp.openById('form ID')
const item = form.getItemById('itemID').asTextItem()
// Get the list of values
const spreadsheet = SpreadsheetApp.openById('Spreadsheet ID')
const sheet = spreadsheet.getSheetByName('Form Responses 1')
const values = sheet.getRange(1, 1, sheet.getLastRow(), 1)
.getValues()
.flat()
// Transform the values into a regex expression (pattern)
const pattern = `^(${values.map(_escapeRegex).join('|')})$`
// Contruct and set validation
const validation = FormApp.createTextValidation()
.setHelpText("Email already exist")
.requireTextDoesNotMatchPattern(pattern)
.build()
item.setValidation(validation)
}
function _escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
Note that this allows users to add non-emails to the field, but not to repeat values.
References
Is there a RegExp.escape function in JavaScript? - Answer by bobince (Stack Overflow)
Class TextValidationBuilder (Google Apps Script reference)
Regular expression (Wikipedia)
Objective & Implementation
I created a questionnair using Google Forms. I want to authenticate users using a simple one time password that should be entered in the first question.
The password is stored inside a spreadsheet that contains only one cell. https://prnt.sc/r8i8q1
The script below reads the content of this cell and create a custom validator against the value of the first item of the form.
function validatePassword() {
var passwordSpreadsheet = "[passwords_spreadsheet_id]";
var ss = SpreadsheetApp.openById(passwordSpreadsheet);
var passwordSheet = ss.getSheetByName("passwords");
var form = FormApp.getActiveForm();
var items = form.getItems();
var item = items[0];
if (item.getType() == 'TEXT') {
var textItem = item.asTextItem();
var namedRanges = passwordSheet.getNamedRanges();
if (namedRanges.length > 0) {
var range = namedRanges[0].getRange();
var values = range.getValues();
var currentPassword = values[0][0];
var textValidation = FormApp.createTextValidation()
.requireTextMatchesPattern(currentPassword)
.build();
textItem.setValidation(textValidation);
}
}
}
Current Situation
The above code is working as expected for one password, but, the problem is that I couldn't find a way to create a custom validator against range of values.
Questions
Is there any way to have this simple authentication mechanisim in Google Forms via Google Apps Script?
Is there a way to make this password a One Time Only password?
If (1.) and (2.) are not available, then, what is the best way to authenticate Google Forms?
Thank you in advance!
Solution
Using Apps Script
The following piece of code will make a password verification according to what you right on the Spreadsheet as a password. It has comments to explain what line of code does:
function validatePassword() {
// Create new custom form
var form = FormApp.create('New Form');
var ss = SpreadsheetApp.openById('SHEETID');
var password = ss.getSheetByName('SHEETNAME').getRange('A1').getValue();
// Create first question to check the password, it must be required so that the user does not have access to the rest
// of the form if failed to log in
var item = form.addTextItem().setRequired(true);
item.setTitle('TEST TITLE');
// Create validation for this question matching the password that we got from the sheet
var textValidation = FormApp.createTextValidation()
.setHelpText('You must enter the right password')
.requireTextMatchesPattern(password)
.build();
item.setValidation(textValidation);
}
Using the UI
The esieast way to validate forms with password is to use the custom functionalities of Google forms. In your case you should follow these steps:
In the first section of your form only place a required short text answer.
Go to the three dots in the lower right part of the question and select Response Validation
Then in the options change them to Regular expression, Matches and introduce your desired regular expression (it could just be a String) and the right error message.
Here is an example of this in action:
I hope this has helped you. Let me know if you specifically need to get the passwords from the Spreadsheet. Let me know if you need anything else or if you did not understood something. :)
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.
I'm looking for a programmatic way to automate the generation of pre-filled URLs for google forms.
In a recent update, Google introduced a new Forms product to Google Docs. If you open the tools menu in a spreadsheet, you'll see some new options.
In the new Form Responses menu, one option is "Get pre-filled URL". This opens up a dialog containing a version of your form that you can fill out, and when you submit it, you receive a URL that can be used to open a Live Form with the data you pre-filled ready and waiting for you. The URL looks something like this...
https://docs.google.com/forms/d/--Form-ID--/viewform?entry.1094330118=Something&entry.1471717973=stack#example.com&entry.540962741&entry.787941281&entry.1873343651
The questions from the form have a fixed identity, e.g. entry.1094330118. They may be pre-filled with a value (entry.1094330118=Something) or blank (entry.7879412).
In apps-script, I'd like to generate these pre-filled URLs for users of my form, so I can provide them for updates. My users are not members of an Apps Domain, so I don't have the option of embedding an Edit your response link.
If I can get the information about the form, I will be able to piece together the URL. While I can go through the UI to create one URL, and dissect it to get the info for that form, I want a solution that will work with arbitrary forms.
How can I programmatically determine the question IDs?
With the new
Forms product, is the form available to me through any apps-script
APIs? (I know about getFormURL() - that's not what I mean.)
Armed with a question ID, can I get more information about the question? (Question text, type, etc.)
I required something similar for users to go and edit their response, take a look here: http://productforums.google.com/forum/#!topic/docs/LSKKCR3VHC8
Copy / paste code below:
function assignEditUrls() {
var form = FormApp.openById('1MonO-uooYhARHsr0xxxxxxxxxxxxxxxxxxxxx');
//enter form ID here
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses');
//Change the sheet name as appropriate
var data = sheet.getDataRange().getValues();
var urlCol = 4; // column number where URL's should be populated; A = 1, B = 2 etc
var responses = form.getResponses();
var timestamps = [], urls = [], resultUrls = [];
for (var i = 0; i < responses.length; i++) {
timestamps.push(responses[i].getTimestamp().setMilliseconds(0));
urls.push(responses[i].getEditResponseUrl());
}
for (var j = 1; j < data.length; j++) {
resultUrls.push([data[j][0]?urls[timestamps.indexOf(data[j][0].setMilliseconds(0))]:'']);
}
sheet.getRange(2, urlCol, resultUrls.length).setValues(resultUrls);
}
This is not possible right now but this request is being tracked on the Issue Tracker here. Please add your use cases there and watch it for updates.
I found no way to create a pre-filled URL from the form id itself. It is possible if the form has already an answer:
var form = FormApp.openById('1nGvvEzQHN1n-----_your_for_id_----zemoiYQA');
var responses = form.getResponses();
Logger.log(responses[0].toPrefilledUrl());
referring to this answer, it can be used to create prefilled urls, by replacing the last line like this:
instead of
FormResponse.submit();
it will be
FormResponse.toPrefilledUrl();