Email notification: TypeError: Cannot find function getColumn in object 1 - javascript

I'm new to javascript and I'm trying to write this function that will be used in google sheet so that whenever a value is changed in a specific cell, it will send an email notification to say that cell has been changed. The problem lies is when I used this code in a google sheet there is an error:
"TypeError: Cannot find function getColumn in object 1"
Im not sure what is causing this and I have attempted to look at other peoples problems similar to this but I just dont understand why it is doing this.
Code:
function onEdit(e)
{
var range = e.range;
if (e.getColumn() == 11)
{
var previous = range.offset(0, -1);
var next = range.offset(0, 1);
var emailAddress = 'myemail#gmail.com';
var message = 'Data Changed';
var subject = 'Test Awareness Mail';
GmailApp.sendEmail(emailAddress, subject, message);
}
}

Related

Google sheets scripts function UrlFetchApp.fetch does not run from .onEdit(e) but works from editor

I have created a google sheet with a lot of info for a beach volleyball cup and I want to call an API I have created when a checkbox is checked in this sheet.
function onEdit(e){
const ui = SpreadsheetApp.getUi();
const spreadsheets = SpreadsheetApp.getActive();
const configSheet = spreadsheets.getSheetByName("Config")
var tourneyId = String(configSheet.getRange(2,4).getValue())
var tourneyTitle = String(configSheet.getRange(2,5).getValue())
var sheet = spreadsheets.getActiveSheet()
if (sheet.getName() == "LiveScore"){
var actRng = sheet.getActiveRange();
var editColumn = actRng.getColumn();
var rowIndex = actRng.getRowIndex();
actRng = actRng.getCell(1, 1);
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
if(editColumn == 7 && rowIndex != 1){
onStartBroadcastClicked(actRng, ui, sheet, rowIndex, editColumn, tourneyTitle);
}
}
}
There is never any problems with this part as I see it. But when i get into the function onStartBroadcastClicked:
function onStartBroadcastClicked(actRng, ui, sheet, rowIndex, editColumn, tourneyTitle){
var homeTeam = String(sheet.getRange(rowIndex, 14).getValue());
... // more setting variables
var endTime = new Date(startTime.getTime() + MILLIS_PER_MATCH);
if(actRng.isChecked()){
var response = ui.alert("You are about to start a new broadcast. Are you sure?" +
"\n Title: " + title, ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
var httpRequest = "https://someUrl";
var options =
{
'method':'POST',
'contentType': 'application/json',
'payload' : JSON.stringify({
"title" : title,
... // setting all variables
"description" : description
}),
'muteHttpExceptions' : true,
'headers' : {
"Authorization": "Basic " + Utilities.base64Encode(USERNAME + ":" + PASSWORD)
}
};
ui.alert("Waiting.......")
var result = UrlFetchApp.fetch(httpRequest, options);
ui.alert(result.getContentText())
The issue is that it always gets to the line ui.alert("Waiting......."), but when triggered from the checkbox, it never succeeds the http POST request. If I click play inside the editor, it succeeds and I got the response in the alertbox.
Could it be some timeout or some autosave issues? Does anyone have any idea if where to keep looking? I've been stuck here for some time now and I would be really happy if anyone can point me to the correct direction.
The modification point of your issue is to use the installable trigger of OnEdit event. When the methods which are required to authorize used at the simple trigger, the error occurs. This situation makes us think that it seems the script doesn't work.
In order to avoid this error, please use the installable triggers of OnEdit event trigger.
As an important point, before you install the trigger, please rename the function name of onEdit() to other name. And install the renamed function name as the OnEdit event trigger. By this, the duplicate run of onEdit() can be prevented. If onEdit() function is installed as the installable trigger, when a cell is edited, the function is run 2 times. Ref.
By above settings, when the cell is edited, UrlFetchApp.fetch() works.
References:
Simple Triggers
Installable Triggers
Asynchronous Processing using Event Triggers
I was able to get a script to work with a trigger if I created the script from script.google.com and call the Google Sheet and tab from the script. I'm manually entered in my API calls per cell within a specified Row:
function fetchUrls() {
var spreadsheetId = "ENTER GOOGLE SHEET ID";
var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
var sheet = spreadsheet.getSheetByName("ENTER SHEET NAME");
var range = sheet.getRange("ENTER RANGE OR FULL COLUMN"); // specify the range of cells in column B
var urls = range.getValues(); // get the values of the cells and store them
in an array
var cache = CacheService.getScriptCache();
for (var i = 0; i < urls.length; i++) {
if(urls[i][0] != "") { // check if the current cell is not empty
var url = urls[i][0];
var result = cache.get(url);
if(!result) {
var response = UrlFetchApp.fetch(url);
result = response.getContentText();
cache.put(url, result, 21600);
}
sheet.getRange(i+1,3).setValue(result); // set the value of the current cell to the result of the API call in column C
}
}
}

Google script project trigger not running?

I'm pretty new to Javascript and have been working on this script to take the most recent entry in a spreadsheet (created from a Google form), match the users email address that is collected to a roster in a second sheet, and send an email to parents. I'm a teacher and the idea is to be able to create a google form that compiles the info students enter and email it to their parents once they submit the form/update the sheet.
I know it's pretty messy...there are some extra variables and things, but the script works perfectly/as expected when you "Run" the script. The only thing is, I have tried to have the script run on a trigger when the form is submitted, but it doesn't. Am I missing something with using triggers?
Code is below:
function createEmail() {
// Sets variables for both sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheets()[0];
var sheet2 = ss.getSheets()[1];
// This gathers information from the most recent entry and write it to an array called newReflectionValues
var reflectionLastRow = sheet1.getLastRow();
var reflectionLastColumn = sheet1.getLastColumn();
var reflectionLastCell = sheet1.getRange(reflectionLastRow, reflectionLastColumn).getValue();
var reflectionRange = sheet1.getRange(reflectionLastRow, 1, 1, reflectionLastColumn);
var newReflectionValues = reflectionRange.getValues();
var studentEmail = newReflectionValues[0][3];
Logger.log("NEW REFLECTION VALUES")
Logger.log(newReflectionValues);
Logger.log("Email will send to student email:")
Logger.log(studentEmail)
// Makes an array of the parent email addresses
var rosterLastRow = sheet2.getLastRow();
var rosterLastColumn = sheet2.getLastColumn();
var rosterEmails = sheet2.getSheetValues(2, 1, rosterLastRow, rosterLastColumn);
Logger.log("PARENT EMAILS")
Logger.log(rosterEmails);
// Cross check emails - if a match, write emails to variable
var parentEntriesLength = rosterLastRow;
for (i = 0; i < parentEntriesLength; i++) {
var currentRange = rosterEmails[i];
if (currentRange[2] == studentEmail) {
var toParents = String(currentRange[3]) + ", " + String(currentRange[4]);
var studentName = String(currentRange[0]);
var countOfReflections = currentRange[6];
break;
} else {
var toParents = "NO PARENT EMAILS FOUND";
}
}
// FINISH EMAIL BELOW
MailApp.sendEmail({
to: toParents,
bcc: "rdoyle#rafos.org" + ", " + String(studentEmail),
subject: "Behavior Reflection Notification",
htmlBody: "<p>Hello,</p>" +
"<p>Today studentName received a behavior reflection for the following action:</p>" +
"<p>newReflectionValues</p>" +
"<p>They took a short break in class and completed the following reflection:</p>" +
"<p>reflectionInformation</p>" +
"<p>" + String(studentName) + " has recieved " + countOfReflections + " reflections this year." + "</p>" +
"<p>This email has been sent with information that the student completed directly on the reflection form and has been bcc'd to them as well as myself. If you have any questions regarding this behavior or incident, please feel free to ask.</p>"
});
}
You are aware that there are 2 types of trigger simple and installable but I think you are a little confused as to what they actually mean/ do. I'll try to explain the key points from documentation here.
A simple trigger is used by simply naming the function with the trigger name. For example, with Sheets / Forms, the trigger onFormSubmit(e) is fired when the user submits a form. The parameter e contains all the information relating to the submission, you should look into this as it's much more reliable than your current method of getting the submitted information. See here: 'e' parameter
Simple triggers are limited in their functionality since the script doesn't have to be authorised for the trigger to fire. A simple trigger cannot access other files, send emails or perform any action that requires authorisation. See here
An installed trigger is one that is either manually set up by the user or a script. Installed triggers have a lot more functionality but they still have some restrictions. See here
An installed trigger can call any named function and e parameter works in the same way as it does with simple triggers.
From your code above your installed trigger should look like this.
When you click save you should be asked for authorisation, if you are not asked, click the debug/ run button to authorise the script.
If it still doesn't work check the execution transcript in view -> execution transcript, the last line will indicate the error.
Ok, James helped out a lot, but I was seeming to have a lot of problems authenticating the permissions to send emails. I decided in the end to re-write everything more clearly, add some functions so others I work with could use the same script, and keep a record of whether or not emails were actually sent to parents. This final version uses a if statement to look in a column at whether or not an email was sent for each response, then sends an email if needed and records when it was sent. I also added a function to set up that "confirmation" column and create a roster sheet as a single function as well as seperately. Oh, and it also looks for an html template to format the email. I found that info on a Google developers live stream: https://www.youtube.com/watch?v=U9Ej6PCeO6s
Thanks everyone!
function createConfirmationColumn() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheets()[0];
var lastColumn = sheet1.getLastColumn();
var lastColumnValue = sheet1.getRange(1,lastColumn).getValue();
// Creates the final column to log the time that emails are sent
if (lastColumnValue != "Email Sent On:") {
sheet1.insertColumnsAfter(lastColumn, 1);
var emailSentOnColumn = sheet1.getRange(1,lastColumn+1).setValue("Email Sent On:");
}
}
function createRosterSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.insertSheet("Roster & Parent Emails", 1);
var rosterSheet = ss.getSheets()[1];
rosterSheet.getRange(1,1).setValue("First Name")
rosterSheet.getRange(1,2).setValue("Last Name")
rosterSheet.getRange(1,3).setValue("Student Email")
rosterSheet.getRange(1,4).setValue("Parent Email 1")
rosterSheet.getRange(1,5).setValue("Parent Email 2")
rosterSheet.getRange(1,6).setValue("Notes")
rosterSheet.getRange(1,7).setValue("Total Reflections")
}
function formSetup() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheets()[0];
var lastColumn = sheet1.getLastColumn();
var lastColumnValue = sheet1.getRange(1,lastColumn).getValue();
// Creates the final column to log the time that emails are sent
if (lastColumnValue != "Email Sent On:") {
sheet1.insertColumnsAfter(lastColumn, 1);
var emailSentOnColumn = sheet1.getRange(1,lastColumn+1).setValue("Email Sent On:");
}
ss.insertSheet("Roster & Parent Emails", 1);
var rosterSheet = ss.getSheets()[1];
rosterSheet.getRange(1,1).setValue("First Name")
rosterSheet.getRange(1,2).setValue("Last Name")
rosterSheet.getRange(1,3).setValue("Student Email")
rosterSheet.getRange(1,4).setValue("Parent Email 1")
rosterSheet.getRange(1,5).setValue("Parent Email 2")
rosterSheet.getRange(1,6).setValue("Notes")
rosterSheet.getRange(1,7).setValue("Total Reflections")
}
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheets()[0];
var sheet2 = ss.getSheets()[1];
var lastColumn = sheet1.getLastColumn();
var lastColumnValue = sheet1.getRange(1,lastColumn).getValue();
var allFormEntries = sheet1.getDataRange().getValues();
var allRosterValues = sheet2.getDataRange().getValues()
for (e = 1; e < sheet1.getLastRow(); e++) {
var formRange = allFormEntries[e];
var studentEmailInForm = formRange[1];
var emailSentOn = formRange[4];
if (emailSentOn == "") {
for (i = 1; i < sheet2.getLastRow(); i++) {
var individualRosterEntry = allRosterValues[i];
if (studentEmailInForm == individualRosterEntry[2]) {
var parentEmails = String(individualRosterEntry[3]) + ", " + String(individualRosterEntry[4]);
var emailTemplate = HtmlService.createTemplateFromFile("emailTemplate");
emailTemplate.studentName = individualRosterEntry[0];
emailTemplate.reflectionCount = individualRosterEntry[6];
emailTemplate.reason = formRange[2];
MailApp.sendEmail({
to: parentEmails,
bcc: "rdoyle#rafos.org" + ", " + String(studentEmailInForm),
subject: "Behavior Reflection Notification",
htmlBody: emailTemplate.evaluate().getContent(),
})
sheet1.getRange((e+1), lastColumn).setValue(new Date());
break;
} else {
sheet1.getRange((e+1), lastColumn).setValue("No valid email found") ;
}
} // for i loop
} //if email sent == ""
} //for e loop
} //function

Using Google App Script to get values from sheets and display them in text box

So, Google recently updated Google App Script API and added lots of nice features, however, in the process, they also depreciated LOTS of API. I have been working on a Library Database user interface for the place I work on my college campus, and when I wanted to update my app to the new API, a lot of things broke, and I can't figure out how to make them work again.
What I am trying to do is get a value from a Google Sheets file, and simply put that value in a text box on the web app. Currently I cannot get that work work. In addition, I discovered something that was troublesome, and that is, the debugger seems to not be correct. I know, bold accusation. Let me try to show you.
Code.gs
function doGet(e) {
var html = HtmlService.createHtmlOutputFromFile('index')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
return html;
}
function searchBooks(searchItem, searchType){
var sI = searchItem;
Logger.log(sI);
var sT = searchType;
Logger.log(sT);
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var ss = sheets[0];
var itemDataRange = ss.getRangeByName("itemInformation");
var selectedItem = null; //the item that will be returned
//var selectedSearch = searchItem;
var titles = sheet.getRange("K2:K9507").getValues(); //get the titles of the items
var authors = sheet.getRange("J2:J9507").getValues(); //get the authors in the sheet
var barcodes = sheet.getRange("B2:B9507").getValues(); //get the barcodes in the sheet
var itemsArray = new Array();
if (sT == '')
{
return null;
}
else if (sT.value == 'Please select type...')
{
var test = "this works";
Logger.log(test);
return selectedItem;
}
else if(sT == 'Barcode')
{
var selectedBarcode = sI;
for(var i = 0; i < barcodes.length; i++) //search for the barcode
{
if(barcodes[i] == selectedBarcode)
{
selectedItem = titles[i];
break; //break immediately because barcodes are not duplicated
}
}
if(selectedItem != null)
{
return selectedItem;
}
else
{
selectedItem = "No book(s) found";
return selectedItem;
}
return selectedItem;
}
}
...
index.html
<script>
function bookSearch()
{
var searchItem = String(document.getElementById('searchItem').value.toLowerCase());
var searchType = String(document.getElementById('searchType').value.toLowerCase());
google.script.run.withSuccessHandler(bookFound).searchBooks(searchItem, searchType);
}
...
function bookFound(selectedItem)
{
document.getElementById("bookResultBox").innHTML = selectedItem;
alert(selectedItem);
}
</script>
When I test this code, and put a search value with the category "Barcodes" selected, I successfully get console logs of the data being brought into the function searchBooks, however the debug console says that the variables sI, sT, searchItems, and searchType are all undefined.
I've also been having trouble trying to figure out the proper API calls to use to search through the spreadsheet (when dealing with stuff like getRangeByName). I think there might be a slightly different way to do this since the big update. I may have had it working before I changed some of the code, although I started changing a lot of it when I was trying to figure out WHY nothing was displaying. When I saw at the "undefined" debug console logs, it scared me a bit. I can't tell if I'm messing up, or the API is messing up.
Any help is much appreciated in advance :)
There's probably an error in your code. It's probably coming from line:
var itemDataRange = ss.getRangeByName("itemInformation");
Your variable ss is not a spreadsheet class, it's a sheet class. You can't get a RangeByName of a sheet class. There is no getRangeByName() method of the Sheet class.
I'd change your code to this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var itemDataRange = ss.getRangeByName("itemInformation");
If you need to get the first sheet:
var theFirstSheet = ss.getSheets()[0];

getting responseID of latest form submission in google apps

I have a google form. Every time it is submitted, the answers go into a google spreadsheet. I am trying to write an app script that triggers every time the form is submitted, and adds an "edit" link in the column to the right of the data from the form. The link itself is easy to generate, google has a method called getEditResponseURL(). (https://developers.google.com/apps-script/reference/forms/form-response)
But everytime I run it, I am getting the error "TypeError: Cannot call method "getResponses" of null."
Here is my code:
function addeditlink(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
//get the form
var form = FormApp.getActiveForm();
//get latest form response
var responses = form.getResponses();
var lastResponse = responses[responses.length - 1];
//get edit URL
var editurl = lastResponse.getEditResponseUrl();
//build link
var editlink = "Edit";
//place edit link in column R (index 18)
if (sheet.getRange(row, 18).getValue() == "") {
sheet.getRange(row, 18).setValue(editlink);
}
}
Any help? Thanks!
The solution is to remove:
var form = FormApp.getActiveForm(); //this is WRONG
and replace with:
var form = FormApp.openByID(' ID here ')
There is no "active form", because this script is being run in sheets, not forms.
I think you can only call FormApp.getActiveForm() from a script attached to a form, whereas your script is contained in a GSheet. I couldn't find a way to easily gets forms that used this sheet as its destination so what I've did was get all of the forms and then looked at the destination id of each and checked if it is the same as this spreadsheet. Once you've got your Form object you can get the responses. Feels a bit long winded would love to know if anyone knows a quicker way.
There are also a few exceptions that FormApp throws that you have to cope with.
Here's the function I use:
/**
* Find the first form that is linked to a specific spreadsheet
*
* #param {string} spreadsheet id
* #return {object} Form or null
*/
function getFormByDestinationId_(spreadsheetId) {
var formFiles = DriveApp.getFilesByType('application/vnd.google-apps.form');
var form;
var formFile;
var formId;
var destinationId;
while (formFiles.hasNext()) {
formFile = formFiles.next();
formId = formFile.getId();
// Throws an error if ID invalid
try {
form = FormApp.openById(formId);
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
// Form.getDestinationId() throws an error if there is no destination id
try {
destinationId = form.getDestinationId();
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
if (destinationId !== spreadsheetId) {
continue;
}
return form;
}
return null;
} // getFormByDestinationId_()
The only line using: getResponses() method is this one:
var responses = form.getResponses();
Your error:
Cannot call method "getResponses" of null
Means that form is null. If form is null, then this line:
//get the form
var form = FormApp.getActiveForm();
is not working. So, why isn't it working? There is nothing wrong with the code, so it must be a different problem. If there was an active form, that code would return a form type. This means that there is no form bound to the script. getActiveForm()
Returns the form to which the script is container-bound.
Your script is not "container-bound" to the form. Your script is bound to the spreadsheet.
The documentation states:
To interact with forms to which the script is not container-bound, use openById(id) or openByUrl(url) instead.
You can bind your script to the form by opening the script editor from the edit page of the form. But, there's no need to do that if you want to keep your script bound to the spreadsheet.
The line var form = FormApp.getActiveForm(); isn't going to work in your spreadsheet script.
The problem with using the Event Object e with an installable trigger, is that it looks like you can't get the response URL.
google_sheets_events
This means that you need to use openById(id) or openByUrl(url) inside the script bound to the spreadsheet, or move all your script to the form.
Here is how to get the edit url from script in the spreadsheet:
// Open a form by ID.
var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
Now the problem is, that you can only get the Edit Response URL: getEditResponseUrl() through the "FormResponse" class. So you need the Form Responses.
var formResponses = form.getResponses();
But that's all the responses, you need the last one.
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
or:
function getEditURLofLastResponse() {
// Open a form by ID.
var form = FormApp.openById('Your Form ID');
var formResponses = form.getResponses();
//get last respnse
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
Logger.log(editURL);
}
Just an observation:
You are using an e argument: function addeditlink(e) {. But I don't see it being used in your code. That makes me wonder if you are using an "installable" trigger, as opposed to a "simple" trigger.
It's possible to get the values that were just submitted with e.values or e.namedValues. But you can't get the Edit URL with the Event Object.

Google script for sending email if cell value is below certain value issue

Very new to javascript. Probably very obvious/easy solution to this for someone that knows.
Trying to write a script that sends an email if a cell value drops below 100 and changes the cell colour to blue:
I have tried to write it by calling a function:
function sendemails() {
var emailAddress = "email#address.com";
var message = "test body";
var subject = "test subject";
MailApp.sendEmail(emailAddress, subject, message);
}
function onEdit(e) {
var range = e.range;
if(range.getValue() < 100) {
range.setBackgroundColor('blue');
}
if(range.getValue() < 100) {
sendemails()
}
}
I have also just tried sending the mail directly:
function onEdit(e) {
var range = e.range;
if(range.getValue() < 100) {
range.setBackgroundColor('blue');
}
if(range.getValue() < 100) {
var emailAddress = "email#address.com";
var message = "test body";
var subject = "test subject";
MailApp.sendEmail(emailAddress, subject, message);
}
}
I cant get either to work, probably as I haven't called the function correctly or have missed something obvious. The cell changes to blue, and if I run the sendemails function by itself, not as a result of onedit it sends an email. Im just struggling to call it.
Any help or explanations would be greatly appreciated!
Thanks
The reason is quite simple, and Google has stated it in the developer guide:
Because simple triggers fire automatically, without asking the user
for authorization, they are subject to several restrictions:
They cannot access services that require authorization. For example, a simple trigger cannot send an email because the Gmail service requires authorization, but a simple trigger can translate a phrase with the Language service, which is anonymous.
Please see the section "Restrictions" here.
To work around this issue, you can create a normal function sendEmail(). This function can be added as a installable trigger, and will then be allowed to call services that require authorization. You can read more about that here.
The code for sending mails would then look like this:
function sendEmail() {
var range = SpreadsheetApp.getActiveRange();
if (range.getValue() < 100){
var emailAddress = "email#example.com";
var message = "test body";
var subject = "test subject";
MailApp.sendEmail(emailAddress, subject, message);
}
}

Categories