Merge Form Submission to document - lost newbie - google-apps-script

Seeking guidance on the following:
User submits Formstack form -> Form entry populates a row on Google Spreadsheet -> Trigger the execution of a document merge for that submission (now a ROW in the sheet) with the column header defining the variables -> Email merged document to User via submitted email address.
My document requires the ability to conditionally handle many of the variables (if/then conditional text based on form response). I've played with an App called Ultradoc which seems great for the variable handling in the Google Document, supports conditions, etc.
However, the problem is it doesn't know how to select one ROW of data, rather it's designed to run a merge for everything in each column.
One idea might be to run a script which takes that new ROW and somehow makes it look like a two row sheet (header+submitted row)?? Somehow hides the other rows? This seems terribly kludgy. What's the right way to approach something like this?
Thanks in advance

You actually don't need any third party apps for this.
Create the form as a Google Form, create the script and make it run on Form submit.
To get the values that were entered into the form, pass the form submission as a parameter to the function: function processForm(e){
To get the data in the form, access the e.values array. It's zero indexed, starting with the top item on the form.
Store the submitted data into variables
var name = e.values[0];
var email = e.values[1];
// and so on...
Perform any validation or document handling.
Create a copy of your template document
var copyId = DocsList.getFileById("templateDocID")
.makeCopy(docName+' from '+name) //or whatever you wanted to call the resulting document
.getId();
var copyDoc = DocumentApp.openById(copyId);
var copyBody = copyDoc.getActiveSection();
Replace the text in the template
copyBody.replaceText("NAME", name);
copyBody.replaceText("EMAIL", email);
// and so on...
Send the document to the user (for my application, I sent as pdf, but you can send it how ever you like)
copyDoc.saveAndClose();
var pdf = DocsList.getFileById(copyId).getAs("application/pdf");
MailApp.sendEmail(email, copyDoc.getName(), "Here's your document", {attachments:pdf});
DocsList.getFileById(copyId).setTrashed(true);

Related

Get Google Forms Responses in an Email Message

I’m writing to you because I’m facing a problem with an automatic email script based on Google Form submission (or new GSheet entry when GForm is sent). Process in short:
I have a Google Form (here's similar one) that is linked to Data Studio and Power BI Dashboards. Based on data that goes into spreadsheets, the forms are automatically pre-filled using calculated fields in dashes (Date, ActionID, Code, ID, Location etc.) and data goes to Dash-linked spreadsheet [Example of what it might look like]. The thing is that in case of errors found during inspections, the copy of the form/email should be also sent to Employee (optionally, the person that is superior to them, so that both know the details, not only the auditor who fills-in the form – just option). I can’t use any available Add-ons for GForms due to sensitive nature of data. Auditors don’t have edit access to Forms/Dashes, and sending all information via email would take too long. I also created a test Google Form to demonstrate what it looks like.
All in all, What I’m looking for is a dynamic script that will also grab company email of the employee (supervisor is optional), and when auditor submits the form, the email/copy of the form also goes to those mentioned aboce. I couldn't find anything GForms related.
I used a similar script before, but don’t think it was based on new rows added to spreadsheets [Example]. If it’s not possible to achieve something like this, even spreadsheet entry form/email would be really greatly appreciated (email based on "EmployeeEmail" from form + selected columns included, as marked in sheet), although I’m afraid of things going wrong in a file with lots of rows. I tried to find something similar and discovered 2 scripts, but I don’t know how to make them dynamic: The first one below just sends messages to emails from a given range and it’s static (range).
thanks! It looks good, but I have 2 issues.
Edit:, it always needs that dummy email ('xxx#xxx.xxx'”, which means I’ll (or sb else) be getting hundreds of emails. Can we somehow get rid of this line or replace it with form sender email (I can replace existing column with one with form sender email (google form add emails automatically)/those that are already in columns 2/3?
2nd thing, how can I concatenate dummy message lines to variables:
var message = row.join('\n'); (currently adds values from row n)
e.g. can we somehow replace text join with e.g.
// Info from specific columns
var Date = lastRowValues[n][5]; //I don’t know how to grab last value from columns, it’s probably something like variable.getLastRow() + variable.getLastColumn() + Get values
var ID = lastRowValues[n][6];
var Code= lastRowValues[n][8];
var Action = lastRowValues[n][9];
var Location = lastRowValues[n][10];
var ErrorType = lastRowValues[n][11];
and then in message
// var = message = “You’ve made an error. Please find the details below.” +
“\n “ +
“\n Date of Action: “ + Date +
“\n ID: “ + ID +
“\n Action Code: “ Code +
“\n Type of Action: “ + Action +
“\n Location: “ + Location +
“\n Error Type: “ + ErrorType +
The rest, such as the title, may stay the same. I realised that numbers only are hard to read. Is it possible to achieve this output? This is what it looks like now.
You can add this code in your linked spreadsheet:
// this function will fires automatically
// it grabs all data from the last row and send it via email
function send_email() {
var row = SpreadsheetApp.getActive().getDataRange().getValues().pop();
var message = row.join('\n');
var subject = 'test subject';
var address = 'xxx#xxx.xxx';
GmailApp.sendEmail(address, subject, message);
// additional emails
var address2 = row[2];
GmailApp.sendEmail(address2, subject, message);
var address3 = row[3];
GmailApp.sendEmail(address3, subject, message);
}
// this function should be run just once to install the trigger
function install_onFormSubmitTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('send_email').forSpreadsheet(ss).onFormSubmit().create();
}
After you install the trigger every time your form was submitted all data from the form (= all cells from the last row on the sheet) will be sent to xxx#xxx.xxx. And to two additional addresses from cells 'C' and 'D'.
Feel free to change the code as you need.

How to get user input in google app script?

Here is what I want to achieve:
I want to delete 'n' number of rows from my google spreadsheet document. This 'n' can vary depending on number of wrong entries inserted in the document (I know this number before running the function). And I want to give myself a flexibility to choose this number (just like console input in C, C++ or any other languages).
Some researching shows solution via SpreadsheetApp.getUi() mode. But it is giving me error: Exception: Cannot call SpreadsheetApp.getUi() from this context.
I don't want to open my spreadsheet as it is huge in size & takes time to load. Purpose of deleting rows pragmatically is that I don't have to open it, else its all 'moo' point.
Another solution could be to just create an variable and change is manually before running script. But it could create bad data if I forget to change that variable someday (I want to make it idiot-proof).
Is there any way to get user input for standalone google app script? and without opening that particular google sheet?
You can always put the script into a blank sheet and treat it as a placeholder for your functions and have the ui prompt pop there. This way, you don't need to open your large sheet. You can always access other sheets when in another via Apps Script. This would be easier and you just need to transfer your script here.
Code:
function showPrompt() {
var ui = SpreadsheetApp.getUi();
var result = ui.prompt(
'Rows to delete?',
'Input:',
ui.ButtonSet.OK_CANCEL);
var button = result.getSelectedButton();
var numRows = result.getResponseText();
if (button == ui.Button.OK) {
// call function and pass the value
deleteSheetRows(numRows);
}
}
function deleteSheetRows(numRows) {
// url of the sheet with data
var url = "https://docs.google.com/spreadsheets/d/***************/";
var sheet = SpreadsheetApp.openByUrl(url);
// do what you need to do here for that sheet using "numRows" value
Logger.log("deleting "+numRows+" rows");
}
Output:
You can create a function in web app just write doGet() or doPost() function and call it with your input.
refer https://developers.google.com/apps-script/guides/web
Take input number of rows which is n in your case, and add your code to delete rows from SpreadSheet.
you can pass input for get by using query parameter like:
?n=4
and you can use n in doGet() method.

How can an apps-script on a Form store extra data into the Sheet?

Q: How can an AppsScript attached to a Form store an extra piece of data into the Sheet?
Situation: We have a (long) Google Form that stores many pieces of data into a Google Sheet. Often the entries need to be edited, and it is much easier to edit using the original form than trying to edit directly into the sheet. (Some of the items are text, several paragraphs long.) I would like to store into the spreadsheet one additional piece of data, specifically the URL that an editor can use to edit the row entry using the form.
I can already get all the form data and I can get the right URL with formResponse.getEditResponseUrl(). And I can send all of that in an email to a user, usually the editor who is collecting all the form entries. (Thanks to many helpful answers in StackOverflow for getting me this far!) But the editor has to manually copy and paste the URL into an additional column in the proper row of the spreadsheet.
I see an interface in class Sheet to add a column to the spreadsheet, but I don't see how to populate that extra column for the particular row that the form just stored. We have added the column manually, and have verified that it is not overwritten by Google when editing via the form. How do I store that one little piece of data into the sheet?
What am I missing? Any help will be greatly appreciated. Thanks.
[added clarifications 2015-02-06]
We have a long form that some people submit and other people edit. Editing is to be done using the form, not editing directly in the spreadsheet, so we need the URL that permits the editors to re-edit the response.
I would like to store that URL into the spreadsheet during the form submission, so that the editors, who have access to the sheet, can find it.
In a script on the Form side, I can easily calculate that URL, but now how do I store it into the sheet in an extra column?
In my Form-side script at the moment, I get the URL and send it, along with all the form data, in an email to the editors' distribution list. One of the editors then copies the URL from the email and pastes it into the sheet. (Most of the time, into the correct row, even. :-) This is a potentially error-prone manual step.)
A secondary question: what is up with the row numbers in the sheet versus the response numbers in the form.getResponses()? The row numbers and response numbers seem to wander as new items are submitted (i.e., new rows), and old items are edited. Can one reasonably predict the sheet's row number in which the editor will find the form data?
Again, thanks for any help you can give me on this. We have a survivable interim solution. However, with a hundred or so form entries coming in the next couple months, I would love to error-proof the process as much as possible.
rick
So, I've just stumbled upon your questions and, hopefully, I've understood it correctly.
Possible problems:
the script is incorrectly bound to the spreadsheet attached to the form and not to the form itself (which is not the problem in your case as far as I understood from your description)
race conditions between submission insertion and additional column edit, or between simultaneous submissions (see lines 27-32 from code)
accessing the spreadsheet directly, without prior selecting a sheet from the spreadsheet, even if it spreadsheet contains only one sheet! (see lines 36-37 from code)
using the column numeric index, instead of the corresponding column letter as argument for getRange() method, which accepts only column letters AFAIK (see lines 42-43 from code)
Below you have the code which should address all these problems (I have not tested it, but it is an adaptation of a perfect working solution for a very similar scenario):
// Converts sheet column numeric index to corresponding column letter
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
The following function must be registered to an "On form submit" event from form - not from the spreadsheet! (Script Toolbar -> Resources -> Current project's triggers -> Add a new trigger)
// Associated the sheet rows with response URLs in an additional column
function onFormSubmit(e)
{
try
{
// Get the response Url, either from FormApp:
var responseUrl = FormApp.getActiveForm().getEditResponseUrl();
// Or alternatively get it from the event:
// var responseUrl e.response.getId().getEditResponseUrl();
// ....................
// Other URL processing
// ....................
// Get a public lock on this script, because we're about to modify a shared resource.
var lock = LockService.getPublicLock();
// Wait for up to 30 seconds for other processes to finish.
lock.waitLock(30000);
// Wait for row insertion to finish, so that sheet.getLastRow() method gets the updated number of rows
Utilities.sleep(1000); // 1 second
// Here insert the URL to your spreadsheet
var spreadsheetUrl = "https://docs.google.com/spreadsheets/d/YGUgHi28_gYUffGYGGH_78hkO1Pk/edit";
// Gets the first sheet inside the spreadsheet (if you have multiple sheets, just change the value [0])
var sheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheets()[0];
// Get updated number of rows and columns, after form submit inserted the new row
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
// Get the exact cell, next to the right of the new row, by converting the column index to corresponding letter
var lastCell = columnToLetter(lastColumn) + lastRow.toString();
// Set the content of the cell with the new URL
sheet.getRange(lastCell).setValue(responseUrl);
// Release the lock so that other processes can continue.
lock.releaseLock();
}
catch (error)
{
// If there's an error, show the error message
return error.toString();
}
}
For any other questions, just write a comment. Hope it helps.
You can use the form submit range parameter to get the row / spreadsheet range of the form data being placed in the sheet. Then use the range offset method to push your data into the column after the last column of form data.
Notice if you use the HYPERLINK formula, you must escape the quotes that are passes as parameters.
e.g.
function formProcessing(e){
var formData = e.values;
var dataRange = e.range; // gets the range on the spreadsheet
/*
do all your processing
*/
var url = "http://www.google.com"; // whatever url to put in spreadsheet
// add the url value to the spreadsheet
formRange.getCell(1,formRange.getLastColumn()).offset(0,1).setValue(url);
// or if you want a named link
//formRange.getCell(1,formRange.getLastColumn()).offset(0,1).setFormula("HYPERLINK(\"" + url + "\", \"Edit Form\")");
}

Dynamically edit multiple choice options in live Google Form using Apps Script

I'm a high school teacher in L.A. trying to create a course registration system using Apps Script. I need the Google Form I'm using for this registration to:
Question 1) Update the choices available in subsequent multiple choice questions on new pages based on a student's current response choices.
Question 2) Eliminate choices from the form when a multiple choice option has reached it's "cap".
Question 1 Example)
A student registers for “tie-tying” in workshop 1, and gets taken to a new page. The Script edits the available choices on that new page based on the student’s first choice, and removes “tie-tying” from the list of possible choices on that new page, so “etiquette” is their only remaining option.
Question 2 Example)
Students can either register for “tie-tying” or “etiquette”, both responses are initially available in the Google Form. 30 students take the survey, all 30 register for the “tie-tying” workshop. The Apps Script references the response spreadsheet, realizes the “tie-tying” workshop is full, then removes it from the Google Form's list of possible choices. Student 31 goes to register, and their only option is “etiquette”.
If my question has already been asked and answered (believe me, I did search!) I'd appreciate the redirection.
I believe we can achieve your second objective without too much difficulty and modify the form, based on the current state of response.
The approach is to
Create the form and associate it with a response spreadsheet
In that response spreadsheet, create a script with a function (updateForm for instance)
Bind that function with the onFormSubmit event, see Using Container-Specific Installable Triggers.
Analyse the response in the updateForm function and modify your form using the Form Service
For instance
function updateForm(e) {
if (e.values[1] == 'Yes') {
Logger.log('Yes');
var existingForm = FormApp.openById('1jYHXD0TBYoKoRUI1mhY4j....yLWGE2vAm_Ux7Twk61c');
Logger.log(existingForm);
var item = existingForm.addMultipleChoiceItem();
item.setTitle('Do you prefer cats or dogs?')
.setChoices([
item.createChoice('Cats'),
item.createChoice('Dogs')
])
.showOtherOption(true);
}
}
When it comes to achieving the goal in your first question, its more delicate, as the form will not submit mid way. What is possible is to go to different pages based on different responses to a Multiple Choice question, your use case may fit this method, although its not very dynamic.
Further its possible to use html Service to create completely dynamic experience.
Let me know if you need further information.
You are not able to create this type of dynamic form using the Google Forms Service, because there is no interaction between the service and scripts during form entry, except upon Form Submission. In the case of a multi-page form, a script has no way to know that a student has completed one page and gone on to another.
You could achieve this using the HtmlService or UiService, though. In either case, you'd rely on the client-side form interacting through server-side scripts to get updated lists of course options, then modifying the next 'page'. It will be complex.
The other answer to this question will keep adding a multichoice select each time for the form is submitted. Using similar approach of:
Create the form and associate it with a response spreadsheet
In that response spreadsheet, create a script with a function (updateForm for instance)
Bind that function with the onFormSubmit event, see Using Container-Specific Installable Triggers.
Analyse the response in the updateForm function and modify your form using the Form Service
I've used the following code to modify a list select which could be easiliy modified for a multiple choice.
function updateForm(){
var form = FormApp.openById('YOUR_FORM_ID'); // Base form
// need to read what dates are available and which are taken
var doc = SpreadsheetApp.getActiveSpreadsheet();
var dates = doc.getRange("dates!A1:A10").getValues(); //available options
var taken_dates = doc.getRange("responses!F2:F51").getValues(); //just getting first 50 responses
// joining the taken dates into one string instead of an array to compare easier
var taken_dates_string = taken_dates.join("|");
var choice = [];
// loop through our available dates
for (d in dates){
// test if date still available
if (dates[d][0] != "" && taken_dates_string.indexOf(dates[d][0]) === -1){
choice.push(dates[d][0]); // if so we add to temp array
}
}
var formItems = form.getItems(FormApp.ItemType.LIST); // our form list items
// assumption that first select list is the one you want to change
// and we just rewrite all the options to ones that are free
formItems[0].asListItem().setChoiceValues(choice);
}

Spreadsheet Email Trigger

I have Written Script on Google Spreadsheet to send Email when spreadsheet is modified or any Data is added. Email Trigger is working but whenever any data is entered in next Row it send Email to previous email address also.
Please suggest solution
The below is written script :
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 1; // Number of rows to process
var dataRange = sheet.getRange(startRow, 1 , numRows,3) // Fetch the range of cells A2:B3
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[2]; // First column
var message = row[0] + "requested" + row [1]; // Second column
var subject = "Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
}
}
Your question is unclear... nowhere in the script I see something that reads which cell is actually modified... your target range is hardcoded on row 2 so the only row that can be processed is row 2 (and the mail can only be sent once)...
So can you :
explain how it should work
explain how it works now , especially what do you mean by 'previous email'
remove typos in your code (row[2] is not First column)
explain how you trigger this function : the name onEdit(e) suggest an onEdit trigger but simple triggers cannot send mail so I suppose you have set some other trigger.
explain why (e) in your function parameter and not using it ?
EDIT : thanks for the complement of information.
The script you suggest is not sufficient to achieve what you want. The idea here is to check if something in the sheet has been modified either by adding (or inserting) a row of data or (if I understood well) by editing any row in the sheet with a new value.
This is not really as simple as it looks at the first glance ;-)
What I would do it to take a 'snapshot' of the sheet and -based on a timer or onEdit - compare that snapshot to the sheet's current state.
There is more than one way to get that result, you could have a second sheet in your spreadsheet that no one could modify and that is a copy of the main sheet that you update after each modification/mail send. So before updating the script should look for any difference between the sheets and send a report to the corresponding email when a difference is found.
Another way to do that is to store the sheet data converted to a string in the script properties, the principle is the same but it's more 'invisible' for normal users accessing the spreadsheet.
You could also use scriptDb or your userproperties but the script properties is probably better suited (simpler) for this use case.
Tell us what you think/prefer and I (or someone else) could probably give you some code to start with.
It appears that you're using a shared spreadsheet to collect the add-user-requests, and trusting the requesters to fill in the information. In the detail document you shared, it further appears that requests are ADDED, but not EDITED. (That's an important simplifying distinction.)
I suggest that what you really need is to use a form for receiving that input. Using a form will create a "data table" within your spreadsheet, a set of columns that you must not mess with. (You can edit the contents, add and delete rows, but must not add or remove columns.) However, you CAN add columns to the spreadsheet outside of this table, which gives you a handy place to store state information about the status of individual requests.
Further, you can trigger your processing to run on form submit, rather than a simple "onEdit" - this gets away from the problem that ScampMichael pointed out. Alternatively, you can use an installable edit trigger, as described in this answer.
Try this sheet, and this form. Save yourself a copy, go into the script and remove the comments that are stopping emails from being sent, and try it out. There's a menu item in the spreadsheet that can kick off processing; just clear the "Request State" column to re-run it. You can open the form (and find its URL), and add more entries to experiment.
It's the core of a similar system that I've written, and contains a discreet state machine for processing the requests. My system has large amounts of very complex data in multiple spreadsheets, so it often gets pre-empted, then needs to run again. (I use a timed trigger for that.) That's why requests are handled through states. If you find that too complex, pull out only the parts you need.