Using Apps Script to create multiple Forms from a Spreadsheet - google-apps-script

I would like to use a list in a spreadsheet to create multiple forms for different classes. I have the simple code to build a new form for each class, but I would like to add some form elements to each of the new forms. I know how to build a single form with the elements I want from a script, and I know how to build a bunch of empty forms with a script pulling the form names from the sheet. But I am struggling with the syntax on the forEach section of this script to add the form items to each new form.
function createForms() {
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet5');
const range = ss.getDataRange().getValues();
range.forEach(item => FormApp.create(item));
This snippet of code creates a new form for each item in sheet 5. Now when this iterates through these new forms, I want to add some items to each form. I am not sure how to get the forEach statement to iterate through the forms and add items to each one.
Any help will be appreciated.
I have tried this but it does not work.
function createForms() {
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet5');
const range = ss.getDataRange().getValues();
range.forEach(item => FormApp.create(item))
const form = FormApp.create(item);
form.addTextItem()
.setTitle('Name')
}
After the answer I received (THANK YOU) I have modified the script and it is working to create the forms as shown from the list in the spreadsheet. Lastly, I would like to have the forms created in a parent folder with a new folder for each form.
function createForms() {
//use this to force email text box to be email address
const emailValidation = FormApp.createTextValidation()
.setHelpText('Input must be email address.')
.requireTextIsEmail()
.build();
const parentFolder = DriveApp.getFolderById('PUT YOUR FOLDER ID HERE');
let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('courses');
let values = ss.getDataRange().getValues();
for (let i = 0; i < values.length; i++) {
let form = FormApp.create(values[i]);
form.addTextItem().setTitle("Name").setHelpText('Please enter your full name').setRequired(true);
form.addTextItem().setTitle("Email Address").setRequired(true).setValidation(emailValidation).setHelpText('An email address is required');
form.addParagraphTextItem().setTitle('Details').setHelpText('Please provide the details you need to explain').setRequired(true);
form.addMultipleChoiceItem().setTitle("Day Choice").setHelpText('Choose the best day for you').setRequired(true)
.setChoiceValues(['Monday', 'Tuesday','Wednesday','Thursday','Friday']);
}
}

The main issue here is in the way you are retrieving the list for the form names.
The getDataRange method will return the range corresponding to the dimensions in which data is present. However, the getValues ends up returning a two-dimensional array of type [][].
While the syntax of the forEach is correct, you are essentially only traversing the array in an unidimensional manner.
It is also important to note the fact that when adding an item to a form, you will have to declare the item of the type you want the form element to be.
Therefore, assuming that the spreadsheet looks like the one below and you want each one form to contain 3 elements, this is how the script should look like:
Spreadsheet
Script
function createForms() {
let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet5');
let values = ss.getDataRange().getValues();
for (let i = 0; i < values.length; i++) {
for (let j = 0; j < values[i].length; j++) {
let form = FormApp.create(values[i][j]);
form.addTextItem().setTitle("Text Item " + values[i][j]);
form.addGridItem().setTitle("Grid Item " + values[i][j]);
form.addMultipleChoiceItem().setTitle("Multiple Choice " + values[i][j]);
}
}
}
Note
I suggest you start by using a simple for loop if you have doubts related to how the forEach works.
However, you can easily switch from for to forEach in the code above.
Reference
getDataRange();
getValues();
create(title).

I have been able with the help from ale13 to figure out what I was wanting to do. Here is the final script. It will create a new form, a new folder in the parent folder for each line of data in the sheet that you specify. It is designed for a single column of names, to create a separate form for each class or group and organize them into new folders in a parent folder. The form types and questions can be modified in the script.
/* This script will create a new form for each line in column A of the sheet by name */
function createForms() {
//use this to force email text box to be email address
const emailValidation = FormApp.createTextValidation()
.setHelpText('Input must be email address.')
.requireTextIsEmail()
.build();
//we will want to save these new forms into a parent google drive folder
//next we will want to create a new folder for each course in this parent folder
// and save the new forms in these child folders with the name of the course
const parentFolder = DriveApp.getFolderById('PUT YOUR FOLDER ID HERE');
let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PUT YOUR SHEET NAME HERE');
let values = ss.getDataRange().getValues();
for (let i = 0; i < values.length; i++) {
let folder = parentFolder.createFolder(values[i]);
let form = FormApp.create(values[i]);
form.addTextItem().setTitle("Name").setHelpText('Please enter your full name').setRequired(true);
form.addTextItem().setTitle("Email Address").setRequired(true).setValidation(emailValidation).setHelpText('An email address is required');
form.addParagraphTextItem().setTitle('Details').setHelpText('Please provide the details you need to explain').setRequired(true);
form.addMultipleChoiceItem().setTitle("Day Choice").setHelpText('Choose the best day for you').setRequired(true)
.setChoiceValues(['Monday', 'Tuesday','Wednesday','Thursday','Friday']);
form.addListItem().setTitle('Rooms').setHelpText('Select your room')
.setChoiceValues(['Room A','Room B','Room C']);
let formFile = DriveApp.getFileById(form.getId());
formFile.moveTo(folder);
}
}

Related

How do I use apps script to programmatically create/submit a google form response on a google form that collects emails? [duplicate]

I have the following issue. I am trying to create a script that will autofill a template google document using the submission of a google form. I am able to get the script to work for questions that are input with text but am struggling on getting the data from questions in the form that are checkboxes (or multiple choice) to work and fill the google document. Any assistance would be great. For example the variable identified as "offense" is from a question with checkboxes that has about 30 different options, I would like each option that is checked on the form to replace text within my google doc. Thanks.
function autoFillGoogleDocFromForm(e) {
//e.values is an array of form values
var timestamp = e.values[4];
var studentName = e.values[3];
var oe = e.values[16];
var gradelevel = e.values[14];
var program = e.values[15];
var offense = e.values[6];
var action = e.values[18];
var serve = e.values[31];
var makeUp = e.values[32];
var comments = e.values[29];
//file is the template file, and you get it by ID
var file = DriveApp.getFileById('1nPWC0IKc1zUJXYxbGahJsSW4uNWwhxnLM8shcD8kEE4');
//We can make a copy of the template, name it, and optionally tell it what folder to live in
//file.makeCopy will return a Google Drive file object
var folder = DriveApp.getFolderById('1FlpHRKqYrEHttA-3ozU3oUVJlgiqqa-F')
var copy = file.makeCopy(studentName + ', ' + timestamp, folder);
//Once we've got the new file created, we need to open it as a document by using its ID
var doc = DocumentApp.openById(copy.getId());
//Since everything we need to change is in the body, we need to get that
var body = doc.getBody();
//Then we call all of our replaceText methods
body.replaceText('<<Student Name>>', studentName);
body.replaceText('<<Incident Date>>', timestamp);
body.replaceText('<<Student Grade>>', gradelevel);
body.replaceText('<<Open enrolled?>>', oe);
body.replaceText('<<IEP/504?>>', program);
body.replaceText('<<Reason for Referral (Handbook)>>', offense);
body.replaceText('<<Administrative Action>>', action);
body.replaceText('<<Date(s) to be Served>>', serve);
body.replaceText('<<Make up Date(s)>>', makeUp);
body.replaceText('<<Comments>>', comments);
//Lastly we save and close the document to persist our changes
doc.saveAndClose();
}
You need to use the labels assigned to the checkboxes to determine if they have been checked. Same for multiple coice.
You can't use ListItems because you can't set the glyph to a check box so I simply insert text with a checkbox character.
I created a form
I then created an onFormSubmit(e) installed trigger in the spreadsheet to get the form response and put it in the Doc. Here I've simply used an active doc to perform my tests. You will need to adjust the script to handle your template doc.
function onFormSubmit() {
// test data
let e = {"authMode":"FULL","namedValues":{"Timestamp":["8/16/2022 14:40:26"],"Student Grade":["Junior"],"Reason for Referrel":["Bad grades, Disruptive in class, Other"],"Student Name":["Joe Smith"],"Open Enrollment":["Yes"]},"range":{"columnEnd":5,"columnStart":1,"rowEnd":2,"rowStart":2},"source":{},"triggerUid":"12151926","values":["8/16/2022 14:40:26","Joe Smith","Junior","Yes","Bad grades, Disruptive in class, Other"]};
try {
let doc = DocumentApp.getActiveDocument();
let body = doc.getBody();
let referrels = ["Bad grades","Unexcused absence","Disruptive in class","Fighting","Other"];
body.replaceText("<<Student Name>>",e.namedValues["Student Name"]);
body.replaceText("<<Student Grade>>",e.namedValues["Student Grade"]);
body.replaceText("<<Open Enrollment>>",e.namedValues["Open Enrollment"]);
// Notice the regex expression below because findText doesn't seem to handle parenthesis well
let text = body.findText("<<Reason for Referral.*>>");
body.replaceText("<<Reason for Referral.*>>","");
if( text ) {
let index = body.getChildIndex(text.getElement().getParent())+1;
referrels.forEach( item => {
let checked = e.namedValues["Reason for Referrel"][0].indexOf(item);
if( checked >= 0 ) {
let listItem = body.insertListItem(index,item);
index = body.getChildIndex(listItem)+1;
}
}
);
}
}
catch(err) {
Logger.log(err);
}
}

Script to autofill google doc from google form using checkboxes

I have the following issue. I am trying to create a script that will autofill a template google document using the submission of a google form. I am able to get the script to work for questions that are input with text but am struggling on getting the data from questions in the form that are checkboxes (or multiple choice) to work and fill the google document. Any assistance would be great. For example the variable identified as "offense" is from a question with checkboxes that has about 30 different options, I would like each option that is checked on the form to replace text within my google doc. Thanks.
function autoFillGoogleDocFromForm(e) {
//e.values is an array of form values
var timestamp = e.values[4];
var studentName = e.values[3];
var oe = e.values[16];
var gradelevel = e.values[14];
var program = e.values[15];
var offense = e.values[6];
var action = e.values[18];
var serve = e.values[31];
var makeUp = e.values[32];
var comments = e.values[29];
//file is the template file, and you get it by ID
var file = DriveApp.getFileById('1nPWC0IKc1zUJXYxbGahJsSW4uNWwhxnLM8shcD8kEE4');
//We can make a copy of the template, name it, and optionally tell it what folder to live in
//file.makeCopy will return a Google Drive file object
var folder = DriveApp.getFolderById('1FlpHRKqYrEHttA-3ozU3oUVJlgiqqa-F')
var copy = file.makeCopy(studentName + ', ' + timestamp, folder);
//Once we've got the new file created, we need to open it as a document by using its ID
var doc = DocumentApp.openById(copy.getId());
//Since everything we need to change is in the body, we need to get that
var body = doc.getBody();
//Then we call all of our replaceText methods
body.replaceText('<<Student Name>>', studentName);
body.replaceText('<<Incident Date>>', timestamp);
body.replaceText('<<Student Grade>>', gradelevel);
body.replaceText('<<Open enrolled?>>', oe);
body.replaceText('<<IEP/504?>>', program);
body.replaceText('<<Reason for Referral (Handbook)>>', offense);
body.replaceText('<<Administrative Action>>', action);
body.replaceText('<<Date(s) to be Served>>', serve);
body.replaceText('<<Make up Date(s)>>', makeUp);
body.replaceText('<<Comments>>', comments);
//Lastly we save and close the document to persist our changes
doc.saveAndClose();
}
You need to use the labels assigned to the checkboxes to determine if they have been checked. Same for multiple coice.
You can't use ListItems because you can't set the glyph to a check box so I simply insert text with a checkbox character.
I created a form
I then created an onFormSubmit(e) installed trigger in the spreadsheet to get the form response and put it in the Doc. Here I've simply used an active doc to perform my tests. You will need to adjust the script to handle your template doc.
function onFormSubmit() {
// test data
let e = {"authMode":"FULL","namedValues":{"Timestamp":["8/16/2022 14:40:26"],"Student Grade":["Junior"],"Reason for Referrel":["Bad grades, Disruptive in class, Other"],"Student Name":["Joe Smith"],"Open Enrollment":["Yes"]},"range":{"columnEnd":5,"columnStart":1,"rowEnd":2,"rowStart":2},"source":{},"triggerUid":"12151926","values":["8/16/2022 14:40:26","Joe Smith","Junior","Yes","Bad grades, Disruptive in class, Other"]};
try {
let doc = DocumentApp.getActiveDocument();
let body = doc.getBody();
let referrels = ["Bad grades","Unexcused absence","Disruptive in class","Fighting","Other"];
body.replaceText("<<Student Name>>",e.namedValues["Student Name"]);
body.replaceText("<<Student Grade>>",e.namedValues["Student Grade"]);
body.replaceText("<<Open Enrollment>>",e.namedValues["Open Enrollment"]);
// Notice the regex expression below because findText doesn't seem to handle parenthesis well
let text = body.findText("<<Reason for Referral.*>>");
body.replaceText("<<Reason for Referral.*>>","");
if( text ) {
let index = body.getChildIndex(text.getElement().getParent())+1;
referrels.forEach( item => {
let checked = e.namedValues["Reason for Referrel"][0].indexOf(item);
if( checked >= 0 ) {
let listItem = body.insertListItem(index,item);
index = body.getChildIndex(listItem)+1;
}
}
);
}
}
catch(err) {
Logger.log(err);
}
}

Getting SubLabels in App Script Function from Gmail

Here the problem:
I have three different categories and I wanted to group them in a unique label with three sublables. Then, i want to pick those sublabels and run my script.
Here the actual situation in Gmail:
as is situation
I want to put all those labels under a main lable nesting them like this:
nested labels
Now, nesting lables in Gmail il very easy but the main problem is when you run a script based on nested labels. Here my function:
function getGmailEmails(){
var label = GmailApp.getUserLabelByName("UtentiSW");
var threads = label.getThreads();
for(var i = threads.length - 1; i >=0; i--){
var messages = threads[i].getMessages();
for (var j = 0; j <messages.length; j++){
var message = messages[j];
if (message.isUnread()){
extractDetails(message);
GmailApp.markMessageRead(message);
}
}
//threads[i].removeLabel(label); //delete the label after getting the message
}
}
If i try to create a nested label and pick it in the function using the name of the label as shown, does't work. It works only if i put the label in the root folder of gmail as per first picture.
Anyone can help? Thanks
Creating Labels and Sub Labels
Create Label before Sub Labels
function createLabelsAndSubLabel() {
const labels = ["Q1","Q1/1","Q1/2","Q1/3"]
labels.forEach(l => GmailApp.createLabel(l))
}
I've never tried it for more that 4 levels but if you want to extend it you can, just remember to create the labels before the sublabel or they all become separate labels and you have to go delete them and start all over again.
What it looks like in Gmail:
Sorry my back is an image
Listing all of your labels in a Spreadsheet
function listAllLabels() {
let labels = JSON.parse(Gmail.Users.Labels.list("me")).labels;
let hdr = ["Name","Vis","Type","id"]
let o = labels.map(obj => [obj.name,obj.messageListVisibility,obj.type,obj.id]);
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
sh.clearContents();
o.unshift(hdr);
sh.getRange(1,1,o.length,o[0].length).setValues(o).sort({column:1,ascending:true});
}
Gmail API needs to be enabled
In regard to your last comment I added this code to create sub labels to an existing label
function createLabelsAndSubLabel() {
const labels = ["Q1/3/1","Q1/3/2","Q1/3/3"]
labels.forEach(l => GmailApp.createLabel(l))
}

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

Make Folders from Spreadsheet Data in Google Drive?

I am just beginning to learn Javascript and Google Apps Script. I have looked at and played with a few scripts, and so am attempting to create my own. What I want to do is take a list of students (Name and Email) and run a script to create "dropboxes", or folders that uses their name and is shared to their email address. This way, they can easily submit work to me in an organized manner. I have written this rough script, which I know will not work.
I was wondering if anyone can give me some tips?
function createDropbox () {
// Get current spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = sh1.getDataRange().getValues();
// For each email address (row in a spreadsheet), create a folder, name it with the data from the Class and Name column, then share it to the email
for(n=1;n<data.length;++n){
var class = sheet.getSheetValues(n, 1, 1, 1);
var name = sheet.getSheetValues(n, 2, 1, 1);
var email = sheet.getSheetValues(n, 3, 1, 1);
var folder = DocsList.createFolder('Dropbox');
folder.createFolder(class . name);
var share = folder.addEditor(email);
}
}
You've got the basic structure of your script right, it's only the semantics and some error handling that need to be worked out.
You need to determine how you want to access the contents of your spreadsheet, and be consistent with that choice.
In your question, you are first getting all the contents of the spreadsheet into a two-dimensional array by using Range.getValues(), and then later trying to get values directly from the sheet multiple additional times using .getSheetValues().
Since your algorithm is based on working through values in a range, use of an array is going to be your most effective approach. To reference the content of the data array, you just need to use [row][column] indexes.
You should think ahead a bit. What will happen in the future if you need to add more student dropboxes? As your initial algorithm is written, new folders are created blindly. Since Google Drive allows multiple folders with the same name, a second run of the script would duplicate all the existing folders. So, before creating anything, you will want to check if the thing already exists, and handle it appropriately.
General advice: Write apps script code in the editor, and take advantage of auto-completion & code coloring. That will help avoid mistakes like variable name mismatches (ss vs sh1).
If you are going to complete the exercise yourself, stop reading here!
Script
This script has an onOpen() function to create a menu that you can use within the spreadsheet, in addition to your createDropbox() function.
The createDropbox() function will create a top level "Dropbox" folder, if one does not already exist. It will then do the same for any students in the spreadsheet, creating and sharing sub-folders if they don't already exist. If you add more students to the spreadsheet, run the script again to get additional folders.
I've included comments to explain some of the tricky bits, as a free educational service!
/**
* Create menu item for Dropbox
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Create / Update Dropbox",
functionName : "createDropbox"
}];
sheet.addMenu("Dropbox", entries);
};
function createDropbox () {
// Get current spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getDataRange() // Get all non-blank cells
.getValues() // Get array of values
.splice(1); // Remove header line
// Define column numbers for data. Array starts at 0.
var CLASS = 0;
var NAME = 1;
var EMAIL = 2;
// Create Dropbox folder if needed
var DROPBOX = "Dropbox"; // Top level dropbox folder
try {
// getFolder throws an exception if folder not found.
var dropbox = DocsList.getFolder(DROPBOX);
}
catch (e) {
// Did not find folder, so create it
dropbox = DocsList.createFolder(DROPBOX);
}
// For each email address (row in a spreadsheet), create a folder,
// name it with the data from the Class and Name column,
// then share it to the email
for (var i=0; i<data.length; i++){
var class = data[i][CLASS];
var name = data[i][NAME];
var email = data[i][EMAIL];
var folderName = class + ' ' + name;
try {
var folder = DocsList.getFolder(DROPBOX + '/' + folderName);
}
catch (e) {
folder = dropbox.createFolder(folderName)
.addEditor(email);
}
}
}
Even though the question is ~6 years old it popped up when I searched for "create folders from sheets data" .
So , taking the answer as inspiration, I had to update the code to allow for changes in the Google API, i.e. no more DocsList.
So here it is.
function createMembersFolders () {
// Get current spreadsheet
var MembersFolder = DriveApp.getFolderById("1Q2Y7_3dPRFsblj_W6cmeUhhPXw2xhNTQ"); // This is the folder "ADI Members"
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getDataRange().getValues() // Get array of values
// Define column numbers for data. Array starts at 0.
var NAME = 1;
// For each name, create a folder,
for (var i=1; i<data.length; i++){ // Skip header row
var name = data[i][NAME];
Logger.log(name);
if(MembersFolder.getFoldersByName(name).hasNext()){
Logger.log("Found the folder, no need to create it");
Logger.log(Object.keys(MembersFolder.getFoldersByName(name)).length);
} else {
Logger.log("Didn't find the folder so I'll create it");
var newFolder = MembersFolder.createFolder(name);
}
}
}
Note that I'm taking the parent folder directly using it's ID (which you get from the URL when viewing the folder).
You could also do this by name using the DriveApp.getFoldersByName("Folder Name").hasNext() condition which checks if the folder exist. If it does exist then you can access that folder you have found via DriveApp.getFoldersByName("Folder Name").next()
I didn't find that use of hasNext and next very intuitive but there it is.