I am trying to send an email to the address supplied in column A when the Status drop down in column H has been set to "Completed". Here is what I have so far:
function onOpen() {
sendemail();
}
// This constant is written in column C for rows for which an email
// has been sent successfully.
var EMAIL_SENT = 'EMAIL_SENT';
var COMPLETED = 'Completed';
/**
* Sends non-duplicate emails with data from the current spreadsheet.
*/
function sendemail() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 400; // Number of rows to process
var dataRange = sheet.getRange(startRow, 1, numRows, 3); //grabbing ranges
of values to get
var data = dataRange.getValues(); //getting values
var status = sheet.getRange(startRow, 8, numRows, 1); //grabbing ranges of
//values to get
var data_status = status.getValues(); //getting values
//logic: if a field is populated and both Column C isn't populated, and
//Status is Completed, populate corresponding row in column C and send email.
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = "";
var emailSent = row[2]; // Third column
if (emailSent != EMAIL_SENT && data_status == COMPLETED) { // Prevents
sending duplicates
var subject = 'Sending emails from a Spreadsheet';
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 3).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is
interrupted
SpreadsheetApp.flush();
}
}
}
This code is heavily influenced from google and I am able to grab all of the info from column H, I'm just unsure of what I am doing wrong. The issue is that it's not working. If I take out the and section of the if statement, it will work just fine, and If I debug the code I can see the array of values given to me from column H. For each "completed" value I need it to send the email, however I don't want the email to be sent if the status is set to completed and the Column C has the value EMAIL_SENT Thank you for your help
I think that your script is almost complete. I think that the script works by modifying one part. So how about this modification?
Modification point :
In your script, data_status is 2 dimensional array. And when the value of each row is compared to "COMPLETED", whole array is comparing.
In order to reflect above to your script, please modify as follows.
From :
if (emailSent != EMAIL_SENT && data_status == COMPLETED) {
To :
if (emailSent != EMAIL_SENT && data_status[i][0] == COMPLETED) {
If this didn't work, please tell me. I would like to modify it.
Related
Beware, I may be overthinking this.
I keep getting into cyclic thought loops when I'm trying to figure out what to do in this situation, So I will try to explain my thinking and where I am at.
Form is filled out on Google Sheets
Form replies are added to the main Form sheet in the "form responses tab"
Code actives, checking to see if the form was filled correctly (columns A and B match)
if they match, it finds the respective google spreadsheet ID that that row needs to go to, by looking at the directory tab.
That item is then sent over to the appropriate list, which is in it's own sheet
This continues for the rest of the rows of the Main QA Forms Responses tab, until all rows have been checked and there are no more entries.
I've been trying to understand this for hours on end, but might be approaching this all from the wrong angle.
As of right now, this is how far i've gotten in the code:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = s.getActiveRange();
var columnSearchNum = 3;
var columnDatastarts = "C";
var formSheetName = "QA Form Responses";
var directorySheetName = "Program Directory";
var matchingProgramSheetIDColumn = 1;
if(s.getName() == formSheetName && r.getColumn(0) == r.getColumn(1)) {
var sourceRow = r.getRow();
var matchingProgram = sourceRow.getRange(0,0).getValue();
var matchingProgramSheetID = s.getName(Programdirectory)....//[code needed1]
//^^^^ I need a line here to pull matching the data inSheetID column where Matching program's string is
//from this code line, Go to that 'program's sheet'
var programSheet = ss.getSheetByID(matchingProgramSheetID);
var programSheetNumRows = programSheet.getLastRow();
//console.log(programSheetNumRows);
var formSheetNumColumns = s.getLastColumn();
var targetRange = programSheet.getActiveRange()
var targetValue = +s.getRange(columnDatastarts+sourceRow).getValue()
//console.log(targetValue);
var programSheetRange = programSheet.getRange(1,columnSearchNum,programSheetNumRows,1);
//console.log(programSheetRange.getNumRows() +" " +programSheetRange.getNumColumns() + " " + programSheetRange.getValues());
var targetRow = findIndex(programSheetRange.getValues(), targetValue);
//console.log(targetRow);
var target = programSheet.getRange(targetRow, 1);
s.getRange(sourceRow, 2, 1, formSheetNumColumns).moveTo(target);
;
}
}
function findIndex(array, search){
//console.log(array);
if(search == "") return false;
for (var i=0; i<array.length; i++){
//console.log("comparing " + +array[i] + " to "+ +search);
if (+array[i] == +search){
return i+1;
}
}
return -1;
}
You want to find a match for a value on the Form Responses sheet with values in a given column on another sheet, and then return the value in the cell adjacent to the matching cell.
There are probably many ways to do this, but the Javascript method indexOf is an obvious choice if using a script. The following is untested, but the logic is sound.
Insert at [code needed1]
// define the Program sheet
var progsheet = ss.getSheetByName(directorySheetName)
// define the first row of data on the program sheet
var firstRowofData = 3
// get the data for columns one and two of the program sheet.
// starting row=3, starting column=1, number of rows = lastrowminus first row plus 1, number of columns = 2
var progsheetdata = progsheet.getRange(firstRowofData,1,progsheet.getLastRow()-firstRowofData+1,2).getValues()
// get Program manager Column
var ProgManager = progsheetdata.map(function(e){return e[0];})
// search the Program manager Column for the first instance of the matching program value
// indexOf returns index if found, or -1 if not found
var result =ProgManager.indexOf(matchingProgram);
if (result == -1){
// couldn't find a matching program
// do something to quit/error message
return
}
else
{
// the id will be in the row returned by IndexOf and in the adjacent column to the right.
var id = progsheetdata[result][1]
}
I've been trying to create a Submit sheet that has 42 rows. They have a border around it indicating where I type my data in. My goal is once Submit is typed in at the end of the row it'll paste that row of data into the bottom of my Database sheet
Here is the code ive been writing and having problems with. Sorry if it looks whacky I'm rookie at this
function submit1(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet");
var range = sheet.getDataRange();
var colVal = "Submit";
var colToSearch = 19;
var dataRangeVals = range.getValues();
for(var i = dataRangeVals.length; i <= 173; i++){
if([i][colToSearch] === colVal){
var sourceVal = sheet.getRow(i+[colToSearch]).getValues();
Logger.log(sourceVal);
var targetSheet = ss.getSheetByName("DataBase");
var lastRow = targetSheet.getLastRow();
targetSheet.getRange(lastRow + 1,1,1,18).setValues(sourceVal);
};
};
};
the var dataRangeVals works and gets all the values of my submit sheet, but when I iterate through it and log sourceVal to check if it grabbed the row of data when I typed Submit into Column T
No values show in the execution log which is probably why nothing gets pasted into my DataBase sheet but it still shows execution complete without any errors which is confusing me . The problem is I don't understand why that's happening.
If you guys could help me out and take a look at it i'd greatly appreciate it.
Here is the link if you'd like it.
Stock Database updated link
Issues:
for(var i = dataRangeVals.length; i <= 173; i++){ will start at 131 and no data will be captured. Instead, replace it with for(var i = 0; i < dataRangeVals.length; i++){.
[i][colToSearch] wont return anything. To read the element of an array it should be dataRangeVals[i][colToSearch].
I tried to print the column T of your sheet and the Submit text has extra space in it. if([i][colToSearch] === colVal){ won't work because Submit is not equal to Submit . Instead use String.match().
sheet.getRow(i+[colToSearch]) wont work because Class Sheet has no getRow() method. Replace getRow() with getRange(row, column, numRows, numColumns)
Code:
function submit1(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet");
var range = sheet.getDataRange();
var colVal = "Submit";
var colToSearch = 19;
var dataRangeVals = range.getValues();
for(var i = 0; i < dataRangeVals.length; i++){
if(dataRangeVals[i][colToSearch].toString().match(colVal)){
var sourceVal = sheet.getRange(i+1, 1, 1, 19).getValues();
var targetSheet = ss.getSheetByName("DataBase");
var lastRow = targetSheet.getLastRow();
sourceVal[0].splice(0,1)//remove the empty first element of subarray
targetSheet.getRange(lastRow + 1,1,1,18).setValues(sourceVal);
};
};
};
Output:
I tested your code and found that on your IF condition if([i][colToSearch] === colVal), the [i][colToSearch] has a null or undefined value as seen here on the execution log result. Thus, your IF statement automatically ends the execution of your code and no values are shown.
I did some tweaks on your code to be able to proceed on the IF statement (to find if the last row matches the word "Submit") then copy the last row data from "Sheet" to the "DataBase" sheet's last row.
Please refer to my sample code here: Updated
function submit1(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet");
var range = sheet.getDataRange();
var targetSheet = ss.getSheetByName("DataBase");
var lastRow = targetSheet.getLastRow()+1;
var colVal = "Submit";
var colToSearch = 20;
var dataRangeVals = range.getValues();
for(var i = 1; i <= dataRangeVals.length; i++){//Loop to check column 20 with "Submit" value
if(sheet.getRange(i,colToSearch).getValue().valueOf(colVal)){//Check each rows on column 20 to find ones that has "Submit" value
for(var x = 2; x<=colToSearch-1; x++){//When a row has "Submit" value, it will get all row data and copies it to "DataBase" sheet
var sourceVal = sheet.getRange(i,x).getValues();
targetSheet.getRange(lastRow,x-1).setValues(sourceVal);
}
lastRow = targetSheet.getLastRow()+1;//Refreshes the new last row on "DataBase" sheet
Logger.log("Done copying row data on \"Sheet\" Row #" + i + " to \"DataBase\"");
}else{
//Do nothing
}
}
}
After running the code, you will be able to see all of the data from the row on "Sheet", copied and added on every last row of the "Database" Sheet. You Should this sample execution log result once it is successful.
Updated Result
Here's my test spreadsheet, based on your spreadsheet design:
Here's the result on DataBase sheet:
I am trying to fix a code I found online. My goal is that once the Summary tab, column I is edited with the drop-down "approved" for the sheet to send an email to the person on the name in column D.
The email is found in the range tab though. This is what I have so far...
var admin_email='taniapeche#gmail.com';
function triggerOnEdit(e)
{
sendEmailOnApproval(e);
}
function checkStatusIsApproved(e)
{ var sheet = SpreadsheetApp.getActive().getSheetByName('Summary');
var range = e.range;
if(range.getColumn() <= 9 &&
range.getLastColumn() >=9 )
{
var edited_row = range.getRow();
var status = SpreadsheetApp.getActiveSheet().getSheetName('Summary').getRange(edited_row,9).getValue();
if(status == 'Approved')
{
return edited_row;
}
}
return 0;
}
function sendEmailOnApproval(e)
{ var sheet = SpreadsheetApp.getActive().getSheetByName('Range');
var approved_row = checkStatusIsApproved(e);
if(approved_row <= 0)
{
return;
}
sendEmailByRow(approved_row);
}
function sendEmailByRow(row)
{
var values = SpreadsheetApp.getActiveSheet().getSheetName('Range').getRange(row,1,row,5).getValues();
var row_values = values[0];
var mail = composeApprovedEmail(row_values);
//SpreadsheetApp.getUi().alert(" subject is "+mail.subject+"\n message "+mail.message);
MailApp.sendEmail(admin_email,mail.subject,mail.message);
}
function composeApprovedEmail(row_values)
{
var first_name = row_values[1];
var last_name = row_values[2];
var email = row_values[3];
var message = "The following mileage is approved: "+first_name+" "+last_name+
" email "+email;
var subject = "Mileage approved "+first_name+" "+last_name
return({message:message,subject:subject});
}
This is how to sheet looks:
https://docs.google.com/spreadsheets/d/1lWORvuwAHducEIiL-VVidJ-wjujE344udPbWCZpE1kw/edit?usp=sharing
Thanks for the help :)
First of all, because you want the script to send an email (an action which requires your authorization), you have to install the edit trigger, either manually or programmatically. If you do it programmatically, you can install the trigger by running this function once:
function createTriggerOnEdit(e) {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("sendEmailOnApproval")
.forSpreadsheet(ss)
.onEdit()
.create();
}
As a result of this, the function sendEmailOnApproval will fire every time the spreadsheet is edited. This function could be something along the following lines (check inline comments for detailed explanation):
function sendEmailOnApproval(e) {
// Get the edited range and sheet using the event object:
var range = e.range;
var editedSheet = range.getSheet();
var textToSearch = "Approved"; // Set which value will cause the email to be sent
// Check that edited cell is in column I, its value is "Approved" and its sheet is "Summary":
if (range.getColumn() === 9 && range.getValue() === textToSearch &&
editedSheet.getName() === "Summary") {
var rowIndex = range.getRow(); // Get index of the edited row
var name = editedSheet.getRange(rowIndex, 4).getValue(); // Get corresponding name in column D
var rangeValues = e.source.getSheetByName("Range").getDataRange().getValues(); // Get values in sheet "Range"
// Iterate through the rows in "Range", looking for the name retrieved from sheet "Summary"
for (var i = 0; i < rangeValues.length; i++) {
var rowValues = rangeValues[i];
if (name === rowValues[0]) { // Check if name matches the one in column D from "Summary"
var mail = composeApprovedEmail(rowValues); // Compose email (your function)
MailApp.sendEmail(admin_email, mail.subject, mail.message); // Send email
return; // End execution so that the script stops iterating through the rows in "Range"
}
}
}
}
Notes:
The function composeApprovedEmail is called in this sample. It's the same as the one you provided. The rest of functions you provided are not used.
Reference:
Installable Triggers
Event Objects: Edit
I'm getting an "undefined" error when I try to get the value from a cell in a spreadsheet. The thing is that if I execute the same command for a different cell I get the value in that cell. The only difference between those 2 cells is the way the value is produced.
The value in the cell that show correctly is produced directly from the Google Form associated with that spreadsheet. The value that doesn't show when called, is produced from a script I created in the Google Form.
Script for the Form (triggered on form submit):
// This code will set the edit form url as the value at cell "C2".
function assignEditUrls() {
var form = FormApp.getActiveForm();
var ss = SpreadsheetApp.openById("my-spreadsheet-id")
var sheet = ss.getSheets()[0];
var urlCol = 3; // column number where URL's should be populated; A = 1, B = 2 etc
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var resultUrl = formResponses[i].getEditResponseUrl();
sheet.getRange(2 + i, urlCol).setValue(resultUrl);
}
SpreadsheetApp.flush();
}
Table (changed to HTML)
<table>
<tr> <!-- Row 1 -->
<td>Timestamp</td> <!-- A1 -->
<td>Name</td> <!-- B1 -->
<td>Edit form URL</td> <!-- C1 -->
</tr>
<tr> <!-- Row 2 -->
<td>5/26/2015 14:04:09</td> <!-- A2: this value came from the form submittion-->
<td>Jones, Donna</td> <!-- B2: this value came from the form submittion-->
<td>https://docs.google.com/forms/d/1-FeW-mXh_8g/viewform?edit2=2_ABaOh9</td> <!-- C2: this value came from the the script in the form -->
</tr>
</table>
Script in Spreadsheet (Triggered on form submit)
function onFormSubmit(e) {
// This script will get the values from different cells in the spreadsheet
// and will send them into an email.
var name = e.range.getValues()[0][1]; // This will get the value from cell "B2".
var editFormURL = e.range.getValues()[0][2]; // This will get the value from cell "C2".
var email = 'my-email#university.edu';
var subject = "Here goes the email subject."
var message = 'This is the body of the email and includes'
+ 'the value from cell "B2" <b>'
+ name + '</b>. This value is retrieved correctly.'
+ '<br>But the value from cell "C2" <b>'+ editFormURL
+ '</b> show as "undefined".';
MailApp.sendEmail(email, subject, message, {htmlBody: message});
}
The email looks like this:
Sented by: my-email#university.edu
Subject: Here goes the email subject.
Body:
This is the body of the email and includes the value from cell "B2" Jones, Donna. This value is retrieved correctly.
But the value from cell "C2" undefined show as "undefined".
Question:
What am I doing wrong?
You've most likely got a race condition in play.
A user submits a form. This is our prime event.
Upon form submission, all triggers that are associated with the event are fired.
assignEditUrls() in the form script, and
onFormSubmit() in the spreadsheet script.
If you had other scripts set up for this event, they would also trigger. The complication here is that all those triggers are fired independently, more-or-less at the same time, but with no guaranteed order of execution. The spreadsheet trigger MIGHT run BEFORE the form trigger! So that's one problem.
Each trigger will receive the event information in the format specific to their definition. (See Event Objects.) Since C2 is not actually part of the form submission, it won't be in the event object received by the spreadsheet function. That's your second problem, but since you know the offset of the value relative to the form input, you can use range.offset() to get it.
An additional wrinkle has to do with the way that Documents and Spreadsheets are shared; each separate script invocation will receive its own copy of the Spreadsheet, which is synchronized with other copies... eventually. Changes made to the spreadsheet by one script will not be immediately visible to all other users. And that makes three problems.
What to do?
You could try to coordinate operations of the two related trigger functions. If they're in the same script, the Lock Service could help with this.
You could have just one trigger function to perform both operations.
Or you could make the spreadsheet function tolerant of any delays, by having it wait for C2 to be populated. This snippet would do that...
...
var editFormURL = null;
var loop = 0;
while (!editFormURL) {
editFormURL = e.range.offset(0,2).getValue(); // This will get the value from cell "C2".
if (!editFormURL) {
// Not ready yet, should we wait?
if (loop++ < 10) {
Utilities.sleep(2000); // sleep 2 seconds
}
else throw new Error( 'Gave up waiting.' );
}
}
// If the script gets here, then it has retrieved a value for editFormURL.
...
One bonus problem: since you're using getValues(), with the plural s, you are retrieving 2-dimensional arrays of information. You're not seeing a problem because when you treat those values like a string, the javascript interpreter coerces the array into the string you've wished for. But it is still a problem - if you want a single value, use getValue().
Get the correct row, then hard code the column with your URL:
var ss = SpreadsheetApp.openById("my-spreadsheet-id")
var sheet = ss.getSheets()[0];
var rowForLookup = e.range.getRow();
var columnOfUrl = 24; //Column X
var theUrl = sheet.getRange(rowForLookup, columnOfUrl).getValue();
So this code is what I finally got after implementing your recommendations. Thanks to Sandy Good and Mogsdad.
function onFormSubmit(e) {
Logger.log("Event Range: " + e.range.getA1Notation());
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var startRow = e.range.getRow();
var startCol = 1;
var numRows = 1;
var numColumns = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, startCol, numRows, numColumns);
var data = dataRange.getValues();
Logger.log("Data Range: " + dataRange.getA1Notation());
for (var i = 0; i < data.length; ++i) {
var column = data[i];
var name = column[4];
var editFormURL = null;
var loop = 0;
while (!editFormURL) {
editFormURL = column[23];
if (!editFormURL) {
// Not ready yet, should we wait?
if (loop++ < 10) {
Utilities.sleep(3000); // sleep 2 second
}
else throw new Error( 'Gave up waiting.' );
}
}
var email = 'my-email#university.edu';
var subject = "Subject here";
var message = 'Some text about ' + name + '.' +
'<br><br>Please view this link: ' +
+ editFormURL;
MailApp.sendEmail(email, subject, message, {htmlBody: message});
}
}
I'm running an email script which works fine, but I only want it to run through the last row with data in it. I don't need it or want it to run if there is no data obviously. How do I reflect that in the example script below?
For example, say there are only 3 rows of data in my sheet. I only need the script to run for those 3 rows but the code will run for 20 rows. The number of entries to process will vary by the day so I can't just set it at a set number.
Basically I'm looking to replace:
var numRows =20;// Number of rows to process
with some iteration of .getlastrow...I think?
Sample script for reference:
function sendEmails() {
var sheet =SpreadsheetApp.getActiveSheet();
var startRow =2;// First row of data to process
var numRows =20;// Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow,1, numRows,2)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for(i in data){
var row = data[i];
var emailAddress = row[0];// First column
var message = row[1];// Second column
var subject ="Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
}
}
Thanks for your help!
You can use the getLastRow() method to get the index of the last populated row in the sheet. Or use getDataRange().getValues and it will only get the rows that have data.
function sendEmails(){
var sheet = SpreadsheetApp.getActiveSheet();
var startRow =2;// First row of data to process
var data = sheet.getDataRange().getValues();
for(var i=startRow; i<data.length; i++){
var row = data[i];
var emailAddress = row[0];// First column
var message = row[1];// Second column
var subject ="Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
}
}