Google app script to construct URL from spreadsheet values and execute it - google-apps-script

I am basically creating a CRM upon a spreadsheet, that is being populated by a google form.
Everytime there is a new entry, I would like to notify an external server by calling a postback URL.
So URL parameters must be taken from the spreadsheet cells of the new entry (the last row), and then executed.
Something like this:
http://www.myserver.com/script.php?value1=[lastrow.cell1]&value2=[lastrow.cell2]....
Then I need to call the URL.
How would you code it?
I Guess this script can be triggered on new line creation on the spreadsheet.

You can use Installable Triggers and UrlFetchApp.fetch() to automate the process of submitting a form to your spreadsheet and then sending an HTTP request to your server containing the form's values.
This is an example of how you would do it (Adapt it to your own form's questions):
// Installable trigger function
function formSubmit(e) {
// Take values from the form submitted
var channelResp = e.namedValues["Channel"];
var question1 = e.namedValues["Question 1"];
// Get values form Parameters sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var parametersSheet = ss.getSheetByName("Parameters");
var parametersArray = parametersSheet.getRange("A2:B4").getValues();
try {
// Verify if the value in channel was well submitted and search for it
var [channel, variable] = parametersArray.find(element => element[0] == channelResp);
// Build the params with your form's answers
var params = `value1=${question1}&value2=${variable}`;
// Add the params to your url
var url = `http://www.myserver.com/script.php?${params}`;
// Make the request
UrlFetchApp.fetch(url);
} catch(e) {
// Throw an exception if the value in channel was not well-submitted
// Or there was an HTTP error
Logger.log(e);
}
}
Notice that e represents the Event Object Form Submit. Now, for setting up the installable trigger, do the following:
1) Go to your Apps Script project
2) Click Edit->Current project's triggers
3) Click "+ Add Trigger"
4) Select :
Choose which function to run -> Function Name
Select event source-> From spreadsheet
Select event type -> On form submit
5) Click Save
Restrictions
Keep in mind the Installable Trigger restrictions
Class UrlFetchApp will not work if you're executing your server as a local server because it will not be able to reach it.

Related

Auto send Email from Sheets Using Google Scrip

I am trying to automate an email system where whenever someone submits a form, therefore editing the spreadsheet, google-scripts will send me an email containing the message. The code works perfectly fine and does what I want. However, I need to manually run the script every time which defeats the purpose. I have tried to do the function onEdit(e) however it never worked. I have also tried the triggers feature of AppsScript with no luck.
/**
* Sends emails with data from the current spreadsheet.
*/
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = sheet.getLastRow();
var numRows = sheet.getLastRow();
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, sheet.getLastColumn());
// Fetch values for each row in the Range.
var data = dataRange.getValues();
var emailAddress = 'email#email.com'; // First column
var message = data[0][3]; // Fourth column;
var subject = 'A Subject';//this will always be the same
Logger.log(emailAddress, subject, message); //This is to test because of the limit of how many emails you can send a day.
MailApp.sendEmail(emailAddress, subject, message);
}
Here is a link to a google sheet that you can test and edit the script on: https://docs.google.com/spreadsheets/d/1bBNDc33fBx2JPcRByt-8TA2vrLqa51H72TIM-SeARsc/edit?usp=sharing
The sheets is not using google forms, it is getting it's data from the results of an HTML webpage I built that is used as a form.
This is possible by using Installable Triggers.
Follow these steps once you created the form inside your sheets:
For New Editor
Open your Apps Script project.
At the left, click Triggers alarm.
At the bottom right, click Add Trigger.
Select and configure the type of trigger you want to create.
Click Save.
For Legacy Editor
From the script editor, choose Edit > Current project's triggers.
Click the link that says: No triggers set up. Click here to add one now.
Under Run, select the name of function you want to trigger.
Under Events, select either Time-driven or the Google App that the script is bound to (for example, From spreadsheet).
Select and configure the type of trigger you want to create (for example, an > > - Hour timer that runs Every hour or an On open trigger).
Optionally, click Notifications to configure how and when you are contacted by email if your triggered function fails.
Click Save.
For this scenario, Select event source: From spreadsheet and Select event type: On form submit.
Once the setup for Installable trigger is done, you can now add an event object e to the function parameter to access the form values submitted by the user.
Try this in your code:
function onSubmitForm(e){
var message = e.namedValues["Message"]; //get the message value using question name
var emailAddress = 'xxxxxsampleemailxxxxxx';
var subject = 'A Subject';
MailApp.sendEmail(emailAddress, subject, message); //send email
}
Example:
Output:
References:
Installable Triggers
Event Object

Reject Google Forms submission or remove response

I'm using the current, "new" version of Google Forms. I have a form, Form A, and the associated script Script A. That script contains a function, onFormSubmit, associated with Form A's form submit trigger. The function receives one argument, an event, which contains the Form and the submitted FormResponse in the fields "source" and "response" respectively.
In the body of this function, how can I prevent the event/reject the form submission?
Alternatively, if this is not possible, how can I quietly prevent the FormResponse from being stored, or quietly remove it from the list of responses?
I see there is a Form.deleteAllResponses method. Do I have to delete all responses and then add all responses back again, except for the current one? Or is there a better way?
Try experimenting with the event trigger and use:
setAcceptingResponses(enabled)
Sets whether the form is currently accepting responses. The default for new forms is true.
Here is a code sample from a related SO post:
function onFormSubmit(){
var af = FormApp.getActiveForm();
var defaultClosedFor = af.getCustomClosedFormMessage();
af.setCustomClosedFormMessage("The form is currently processing a submission, please refresh the page.");
af.setAcceptingResponses(false);
<put your script stuff here>
af.setAcceptingResponses(true);
af.setCustomClosedFormMessage(defaultClosedFor);
}
Hope this helps.
The way I have handled this is by having a second sheet (tab within the same Google Sheet) into which Form responses are copied. The first thing that happens in the onFormSubmit() function is that the newest row in the responses sheet is copied to the duplicate sheet. You could implement a selection statement that chooses whether to make the copy or not depending on your criteria.
This means that there is always a copy of the raw responses from the form (important for auditing purposes) but also a means for correcting/modifying responses if errors were made by the submitter.
In case it is useful, this is my function that does the copy (note that my settings object is abstracted elsewhere but hopefully there is enough to make clear what is going on).
/**
* Copies form submissions from the responses sheet to another sheet.
*
* #param {event object} e the event object received from a form submit trigger
* #param {Settings} settings an object containing the settings this function expects to use
* #return {integer} the position of the new row in the destination sheet
*/
function copyFormSubmissionToSheet(e, settings) {
var sourceSheet = SpreadsheetApp.getActive().getSheetByName(settings.nameOfFormResponsesSheet);
var destinationSheet = SpreadsheetApp.getActive().getSheetByName(settings.name OfApprovalsSheet);
var newRow = e.range["rowStart"];
var columnCount = sourceSheet.getLastColumn();
var newResponseRange = sourceSheet.getRange(newRow, 1, 1, columnCount);
var newResponseDestinationRange = destinationSheet.getRange(destinationSheet.getLastRow()+1, 1, 1, columnCount);
newResponseRange.copyTo(newResponseDestinationRange);
var newDataSheetRow = destinationSheet.getLastRow();
return newDataSheetRow;
}

Google Sheets Auto-Populate the author for each row on change of column B

How can I add an auto-populate of the user who last edited the row to the following code that auto-populates the date the column was last edited. Here is the current script:
function onEdit(e) {
var s = e.source.getActiveSheet(),
cols = [2],
colStamp = 1,
ind = cols.indexOf(e.range.columnStart)
if (s.getName() !== 'Log' || ind == -1) return;
e.range.offset(0, parseInt(colStamp - cols[ind]))
.setValue(e.value ? new Date() : null);
}
So, ultimately... I would want to add the user name to column G on edit of each row. Any ideas?
You can retrieve user name using Class Session. The detail information is here. https://developers.google.com/apps-script/reference/base/session
Please add following script to last row of your script. The user name is imported to column G. If you want user e-mail, please change from getUsername() to getEmail().
Script :
s.getRange(e.range.getRow(), 7).setValue(Session.getEffectiveUser().getUsername());
About onEdit()
If the onEdit() is not installed as a trigger, other shared users cannot use Session.getEffectiveUser().getUsername() because of authMode=LIMITED. So the user name cannot be retrieved. By installing onEdit() as a trigger, authMode becomes FULL. So you can retrieve user data using Session.getEffectiveUser().getUsername().
The detail information for installing trigger is https://developers.google.com/apps-script/guides/triggers/installable#managing_triggers_manually
If I misunderstand your question, I'm sorry.
Added 1 :
In order to retrieve user information, the user has to install a trigger for onEdit(). I had forgotten about this. I'm sorry.
For example, owner of spreadsheet and user with a permission for editing are OWNER and USER, respectively. When OWNER installs a trigger for onEdit(), the user of spreadsheet becomes OWNER. At this time, when USER edits the spreadsheet, the user name becomes OWNER.
When I have worked a test, I have installed as USER. So I had thought that it works. But it was wrong. So I thought for the solution as follows.
Install a trigger for onEdit() using onOpen().
This didn't work, as you know.
Display a dialog box and a button using onOpen(). Install a trigger for onEdit() by the button.
This didn't work, because of existing several triggers for each user.
Display a dialog box and a button using onOpen(). while temporarily install a trigger for onEdit(), retrieve the user name and put it to cache by the button. The trigger is removed after retrieved user name soon.
This works fine.
I propose the 3rd method. In this script, it is not necessary to install triggers manually. If you want to change the hold time of cache, please modify cache.put().
Script :
function getUser() {
var triggerId = ScriptApp.newTrigger('onEdit')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create().getUniqueId();
var user = Session.getEffectiveUser().getUsername();
var triggers = ScriptApp.getProjectTriggers();
for (i in triggers) {
if (triggers[i].getUniqueId() == triggerId) {
ScriptApp.deleteTrigger(triggers[i]);
}
}
var cache = CacheService.getUserCache();
cache.put("username", user, 3600); // For example, hold user name for 1 h
}
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet().show(
HtmlService
.createHtmlOutput('<input type="button" value="OK" onclick="google.script.run.withSuccessHandler(function(){google.script.host.close()}).getUser()">')
.setTitle('Push OK button.')
.setWidth(400)
.setHeight(100)
);
}
function onEdit(e) {
var cache = CacheService.getUserCache();
var user = cache.get("username"); // Please use this as user name.
}
Flow of script :
When spreadsheet is opened, a dialog is opened on the spreadsheet by onOpen().
When user pushes "ok", the user name is retrieved and put to the cache by getUser().
When user edits the spreadsheet, the user name is retrieved from the cache by onEdit().
Please copy and paste this script. You can use user in onEdit().
When I have been confirming this again, I noticed that in order to use this script, each user has to be authorized. The authorization is https://developers.google.com/apps-script/guides/services/authorization
Added 2 :
I report a solution for retrieving shared user information at spreadsheet. It was found as follows.
User information retrieving by Class Session is the owner and users which installed triggers by themselves.
When each user installs a trigger, user information retrieving by Class Session losts the accuracy. So user information has to be retrieved using a temporally installed trigger.
Using onOpen(), it cannot directly install triggers and authorize.
Using menu bar, it can install triggers and authorize Google Services using API.
Here, I thought 2 problems.
The confirmation whether the authorization was done.
At onOpen(), although many methods using Google API can be executed without the authorization, there are also some methods which cannot be executed without the authorization. Furthermore, there are some methods which cannot execute even if the authorization was done. It's trigger. On the other hand, DriveApp requires the authorization for only the first time, but it can use without the authorization after 2nd times.
I thought that users can find easily by displaying information in a dialog box when spreadsheet is launched. So I adopted displaying information using the dialog box. But, there is a big limitation for the dialog box.
Using a click of button on a dialog box, it can install triggers. However it cannot authorize Google Services using API.
Using above information, I thought a flow to retrieve user information.
When user opens the spreadsheet for the first time, it displays 'Please authorize at "Authorization" of menu bar.' using a dialog box, and creates a menu bar "Authorization".
The user clicks "OK" button on the dialog box and run "Authorization" at the menu bar. By running "Authorization", the user information is retrieved by a temporally installed trigger.
When the user opens the spreadsheet after the 2nd time, the authorization is checked by DriveApp. A dialog box with 'Push OK button.' is displayed. By clicking "OK", the user information is retrieved by a temporally installed trigger.
By this flow, the user information which is using the shared spreadsheet can be retrieved. Although I think that there may be also other solutions, I proposal this as one of solutions.
Script :
function getUser(){
var triggerId = ScriptApp.newTrigger('getUser')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create()
.getUniqueId();
var userInf = Session.getEffectiveUser();
var userName = userInf.getUsername();
var userMail = userInf.getEmail();
var triggers = ScriptApp.getProjectTriggers();
[ScriptApp.deleteTrigger(i) for each (i in triggers) if (i.getUniqueId() == triggerId)];
CacheService.getUserCache().putAll({
"username": userName,
"usermail": userMail
}, 3600);
}
function dialogForGetUser(){
SpreadsheetApp.getActiveSpreadsheet().show(
HtmlService
.createHtmlOutput('<input type="button"\
value="OK"\
onclick="google.script.run.withSuccessHandler(function(){google.script.host.close()})\
.getUser()">'
)
.setTitle('Push OK button.')
.setWidth(400)
.setHeight(100)
);
}
function dialogForAuth(){
SpreadsheetApp.getActiveSpreadsheet().show(
HtmlService
.createHtmlOutput('<input type="button"\
value="OK"\
onclick="google.script.host.close()">'
)
.setTitle('Please authorize at "Authorization" of menu bar.')
.setWidth(400)
.setHeight(100)
);
}
function getAuth() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.removeMenu("Authorization");
getUser();
ss.toast("Done.", "Authorization", 3);
}
function onOpen(){
try {
var temp = DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId());
dialogForGetUser();
} catch(e) {
dialogForAuth();
SpreadsheetApp.getActiveSpreadsheet().addMenu(
"Authorization",
[{
functionName:"getAuth",
name:"Run this only when the first time"
}]
);
}
}
function onEdit(e){
var cache = CacheService.getUserCache();
var user = cache.getAll(["username", "usermail"]);
// user.username is user name.
}
When the spreadsheet is opened, at first, onOpen() is executed. It is checked whether the user has already authorized.
If the user has never authorized yet, dialogForAuth() is executed. If the user has already authorized. dialogForGetUser() is executed.
In this case, you can retrieve user name by user.username at onEdit().

e.source returns Spreadsheet instead of Form object?

I create google form 'on the fly' using data in spreadsheet. Also I install trigger on submit form event.
ScriptApp.newTrigger('onSubmit')
.forForm(form)
.onFormSubmit()
.create();
onSubmit function placed in the spreadsheet script because there is no way to point the function on the form's side (I make the copy of existent form with script code but it is no use as I can't make that functions run).
Well, I process the submission event on the spreadsheet side. No problem. But when I tried to get the source of 'e' object:
function onSubmit(e) {
var response, items, i, item, hash, answer, id;
var sheet, arr, source;
sheet = SpreadsheetApp.openById(RESPONSE_SS_ID).getSheetByName(RESPONSE_SHEET);
response = e.response;
source = e.source;
Logger.log(e);
...
I get not the Form object as promissed in manual, but Spreadsheet object
Logger.log([{response=FormResponse, source=Spreadsheet, triggerUid=4071774310898422364, authMode=FULL}
Perhaps, I'm doing something wrong? How to get the Form source properly in this case?
Clearly the form is not behaving as its documentation says it does, which has been documented in Google Code Issue 4810
Luckily, there is at least one workaround, provided in the comments on that issue, which is to use the getEditResponseUrl method of the response to get to the form. Here is my implementation of the fix in the form of a function that fixes up the event object to add the missing source:
function fixBrokenEvent (event) {
if (! event.source ) {
var responseEditUrl = event.response.getEditResponseUrl(); //gets edit response url which includes the form url
var responseUrl = responseEditUrl.toString().replace(/viewform.*/,''); //returns only the form url
event.source = FormApp.openByUrl(responseUrl); //gets the submitted form id
}
return event
}
This workaround does the trick for me. Another solution would be to use the Trigger UID and search through the list of triggers from ScriptApp.getProjectTriggers() for the right trigger UID.
Something like...
function fixEventWithTriggers (event) {
ScriptApp.getProjectTriggers().forEach(function (trigger) {
if (trigger.getUniqueId()==event.triggerUid) {
event.source = FormApp.openFormById(trigger.getSourceId())
return event
}
}
}
This last workaround comes from Comment #5 on Issue 3786

Event Object not working in google app script for onSubmit trigger in google form

I created a google form and added a trigger which triggers whenever the form submit event is triggered. I need to use the event object for this event and when I add any line of code which tries to access this event then, an error occurs.
function onSubmit(e) {
var s = e.values[0];
Logger.log(s);
}
I get this error message when the function is triggered:
Execution failed: TypeError: Cannot read property "0" from undefined. (line 2, file "Code")
My form has one text input field (basically its just a form where I'm testing and trying out things with Google App Script), so I'm trying to access the data in this field when the form is submitted.
You can use the ActiveForm object instead of the event object.
function onSubmit() {
var responses = FormApp.getActiveForm().getResponses();
var length = responses.length;
var lastResponse = responses[length-1];
var formValues = lastResponse.getItemResponses();
Logger.log(formValues[0].getResponse());
}
This code does basically what you need (after you set up the trigger like you did).
Better explanation can be found here: google script get the current response onSubmit