I am following these tutorials on how to create an email and populate it with values from a Google Sheet.
https://developers.google.com/apps-script/articles/sending_emails
https://katydecorah.com/code/google-sheets-to-gmail/
It all works as expected, however I cannot apply any formatting to the email such as underline, bold or even tables. Any formatting I apply in the original Google Doc is removed when the email is generated.
EDIT:
Here is the Google-Doc used as the template
Dear {caregiver}
Herewith please find the Weekly Engagement Summary for {fname} {sname}
Week 1: {week_1_score}
Week 2: {week_2_score}
Thank You
And here is the Google-Sheet used with the values
fname|sname|caregiver|email|week_1_score|week_2_score|date drafted
bob|smith|parent1|user1#gmail.com|4|3
john|jones|parent2|user2#gmail.com|2|4
rob|brown|parent3|user3#live.com|3|5
And here is the code-behind script that glue them together and creates the email
// What is the Google Document ID for your email template?
var googleDocId = "<my id>";
// Which column has the email address? Enter the column row header exactly.
var emailField = 'email';
// What is the subject line?
var emailSubject = 'Weekly Engagement Indicator';
// Which column is the indicator for email drafted? Enter the column row header exactly.
var emailStatus = 'date drafted';
/* ----------------------------------- */
// Be careful editing beyond this line //
/* ----------------------------------- */
var sheet = SpreadsheetApp.getActiveSheet(); // Use data from the active sheet
function draftMyEmails() {
var emailTemplate = DocumentApp.openById(googleDocId).getText(); // Get your email template from Google Docs
var data = getCols(2, sheet.getLastRow() - 1);
var myVars = getCols(1, 1)[0];
var draftedRow = myVars.indexOf(emailStatus) + 1;
// Work through each data row in the spreadsheet
data.forEach(function(row, index){
// Build a configuration for each row
var config = createConfig(myVars, row);
// Prevent from drafing duplicates and from drafting emails without a recipient
if (config[emailStatus] === '' || config[emailStatus] !== '' && config[emailField]) {
// Replace template variables with the receipient's data
var emailBody = replaceTemplateVars(emailTemplate, config);
// Replace template variables in subject line
var emailSubjectUpdated = replaceTemplateVars(emailSubject, config);
// Create the email draft
GmailApp.createDraft(
config[emailField], // Recipient
emailSubjectUpdated, // Subject
emailBody // Body
);
sheet.getRange(2 + index, draftedRow).setValue(new Date()); // Update the last column
SpreadsheetApp.flush(); // Make sure the last cell is updated right away
}
});
}
function replaceTemplateVars(string, config) {
return string.replace(/{[^{}]+}/g, function(key){
return config[key.replace(/[{}]+/g, "")] || "";
});
}
function createConfig(myVars, row) {
return myVars.reduce(function(obj, myVar, index) {
obj[myVar] = row[index];
return obj;
}, {});
}
function getCols(startRow, numRows) {
var lastColumn = sheet.getLastColumn(); // Last column
var dataRange = sheet.getRange(startRow, 1, numRows, lastColumn) // Fetch the data range of the active sheet
return dataRange.getValues(); // Fetch values for each row in the range
}
Any help appreciated.
Thanks
Related
I'm looking for a code I can use in google sheets.
I need to get a notification when a cell changes in a specific column and get it through email or Slack.
Can someone please help me?
I'm currently using
function onSpeEdit(e) {
var sh = e.source.getActiveSheet();
var rng = e.source.getActiveRange();
var col = 1
if (sh.getName() == 'mySheet' && rng.getColumn() == col) {
MailApp.sendEmail(
'yourEmail#gmail.com',
`Change Notification`,
`Change in ${rng.getA1Notation()} old value "${e.oldValue}" new value "${e.value}" `);
}
}
Try
function onSpeEdit(e) {
var sh = e.source.getActiveSheet();
var rng = e.source.getActiveRange();
var col = 1
if (rng.getColumn() == col) {
MailApp.sendEmail(
'yourEmail#gmail.com',
`Change Notification`,
`Change in ${rng.getA1Notation()} of ${sh.getName()} old value "${e.oldValue}" new value "${e.value}" `);
}
}
change name of sheet, column and email address
you will need to define an installable trigger in order to use services that requires authorization.
Installable Triggers
edit : il you want to add another information, i.e. from column B, try to replace the sentence by
`Hey, the title ${sh.getRange('B'+rng.getRow()).getValue()} from sheet ${sh.getName()} changed. Before it has the number ${e.oldValue} now is ${e.value}.`
I'd like to have a user click/highlight a column (or a checkbox at the top of the column, then click a single button to run the script on all checked columns.
var values1 = "Pass";
// Where to look for Auto:
var enabledDisabled = sheet.getRange("B3:B140").getValues();
var testResultsRange = sheet.getRange("AL3:AL140");
var testResults = testResultsRange.getValues();
// Keyword to look for in Auto: column
var putValues = [];
for (var i = 0; i < enabledDisabled.length; i++) {
if (enabledDisabled[i][0] === "Yes") {
putValues.push([values1]);
} else {
putValues.push([testResults[i][0]]); // Push the existing cell value
}
}
// Put value1 inside row, column# for test result
testResultsRange.setValues(putValues);
Instead of hardcoded ranges, I'd like it to run on every column that is checked, basically.
Any help would be hugely appreciated!
One solution I usually use is to put checkboxes on the first row of the columns, then with onEdit I run the script by clicking on the checkbox and by detecting the column by getColumn ()
function onEdit(event){
var sh = event.source.getActiveSheet();
var cel = event.source.getActiveRange();
if (sh.getName()=='mySheet' and cel.getValue()){
var col = cel.getColumn()
// run your script in column 'col'
cel.setValue(!cel.getValue())
}
}
I am trying to run a script that will execute when the checkbox is marked "TRUE" and send an email to a client. I can not get the triggers to work and I can seem to make my own work either. I am putting the code below. Please help.
'''code'''
***Email Function
function SendEmail(i) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//var lr= ss.getLastRow();
//for (var i = 2; i<=lr;i++){
var currentEmail= ss.getRange(i, 1).getValue();
//logger.log(currentEmail);
var currentADID= ss.getRange(i, 3).getValue();
MailApp.sendEmail(currentEmail,"Customer Ready"+ currentADID,"This customer should be ready to schedule and installtion date and time" );
}
Trigger Function
function CheckCD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr= ss.getLastRow();
for (var i = 2; i<=lr;i++){
var currentDCheck= ss.getRange(i, 4).getValue();
var x= onEdit():
if (currentDCheck == true)
SendEmail(i)
}
}***
You actually misunderstood how to use onEdit trigger, although you need to install the trigger in this case due to permission needed by MailApp.sendEmail
Anyways, you need to make use of the event object (e) as it holds the details about the edited cell's properties. See code below:
Code:
// Trigger function, install it under "Triggers" tab as you need permission for MailApp
function CheckCD(e) {
// get the row and column of the edited cell
var row = e.range.getRow();
var column = e.range.getColumn();
// proceed only if edited cell's range is D2:D and value is "TRUE"
if (row > 1 && column == 4 && e.value == "TRUE") {
// get the sheet where the cell was edited
var ss = e.source.getActiveSheet();
// get Email and ADID of the same row where checkbox was ticked
var currentEmail = ss.getRange(row, 1).getValue();
var currentADID = ss.getRange(row, 3).getValue();
MailApp.sendEmail(currentEmail, "Customer Ready" + currentADID, "This customer should be ready to schedule and installtion date and time");
}
}
Install trigger:
Make sure to choose On edit event type and choose the function you want to run. (in this case, CheckCD)
Sample data:
Output:
Note:
The behavior of this script is that every time someone checks a checkbox on range D2:D and the resulting value is TRUE, then it sends an email. (as it seems you have different email per row, but if not, send them all in one email)
References:
Installable Triggers
I know there are a lot of questions out there related to Google Scripts and some of them touch on my issue but I can't seem to figure out the full answer.
I only want to be able to run 1 or 2 scripts in the same project file for 1 spreadsheet. How do I allow permission for everyone with whom the sheet is shared WITHOUT forcing each user to go through the process of allowing Authorization? Is this possible?
Maybe there is an alternative script? Here is my script (in case this helps):
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{name:"Go to active row", functionName:"Go to active
row"}];
sheet.addMenu("Scripts", entries);
myFunction();
};
function gotoend() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow = sheet.getDataRange().getValues();
lastRow.forEach(function (row,index) {
if (row[3] == "") { // row[0] is the same as Column A. row[1] = B
lastRow.length = index;
}
});
var newRange = sheet.getRange(lastRow.length,1);
sheet.setActiveRange(newRange);
}
/**
* #OnlyCurrentDoc
*/
When working with google spreadsheet, how to download all the sheets at once?
I want to use the option:
Comma-separated values
But it only download the current sheet, how to get them all?
For anyone who navigates to this question, trying to download all the tabs in their Google spreadsheets as CSV files at once, even in 2021, there does not seem to be a GUI button to do this. At least I could not see anything. The answer by #Amit Agarwal does well, to get all sheets, but if your file has comma-delimited data in cells, then data could get mangled.
I took Amit's approach https://stackoverflow.com/a/28711961 and combined it with Michael Derazon and Aaron Davis's approach here https://gist.github.com/mrkrndvs/a2c8ff518b16e9188338cb809e06ccf1 to dump all the tabs of a chosen Google spreadsheet into a folder in Google Drive. You can then just download the folder with a single click.
The following is Google script, not exactly a Javascript, and you would have to copy-paste this in https://script.google.com/ login with your Google id, and then create a project and then create a script app, and save and execute this.
// https://stackoverflow.com/a/28711961
function export_sheets_as_csv_to_folder() {
// Sheet id is in URL https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit#gid=IGNORE
var ss = SpreadsheetApp.openById('YOUR_SHEET_ID');
var sheets = ss.getSheets();
if (sheets === undefined || sheets.length === 0) {
return;
}
var passThroughFolder = DriveApp.createFolder('YOUR_PREFERRED_FOLDER_NAME_IN_DRIVE');
for (var s in sheets) {
var csv = convertRangeToCsvFile_(sheets[s])
passThroughFolder.createFile(sheets[s].getName() + ".csv", csv);
}
}
// https://gist.github.com/mrkrndvs/a2c8ff518b16e9188338cb809e06ccf1
function convertRangeToCsvFile_(sheet) {
// get available data range in the spreadsheet
var activeRange = sheet.getDataRange();
try {
var data = activeRange.getValues();
var csvFile = undefined;
// loop through the data in the range and build a string with the csv data
if (data.length > 1) {
var csv = "";
for (var row = 0; row < data.length; row++) {
for (var col = 0; col < data[row].length; col++) {
if (data[row][col].toString().indexOf(",") != -1) {
data[row][col] = "\"" + data[row][col] + "\"";
}
}
// join each row's columns
// add a carriage return to end of each row, except for the last one
if (row < data.length-1) {
csv += data[row].join(",") + "\r\n";
}
else {
csv += data[row];
}
}
csvFile = csv;
}
return csvFile;
}
catch(err) {
Logger.log(err);
Browser.msgBox(err);
}
}
After clicking download > pdf, select export > worksheet (instead of current sheet which is the default)
You can use Google Scripts to save all the sheets of a spreadsheet into separate files.
function myFunction() {
var ss = SpreadsheetApp.openById(SHEET_ID);
var sheets = ss.getSheets();
for (var s in sheets) {
var csv = "";
var data = sheets[s].getDataRange().getValues();
for (d in data) {
csv += data[d].join(",") + "\n";
}
DriveApp.createFile(sheets[s].getName() + ".csv", csv);
}
}
the answer from #Soham works amazingly but it doesn't handle multiline values. It would be an easy fix just to add more checks to character \n along with , but I took the liberty to rewrite the function using map (and string.includes) so it is more concise.
function convertRangeToCsvFile_(sheet) {
return sheet.getDataRange().getValues()
.map(row => row.map(value => value.toString())
.map(value => (value.includes("\n") || value.includes(",")) ? "\"" + value + "\"" : value)
.join(','))
.join('\n')
}
A slight variation on this that uses a zip instead of a folder to contain the sheets and does some modernizing of the great work done by keychera and Soham's answer.
You can use this as a bound script and it will add a menu item to the extensions menu:
// Code.gs
function exportSheetsToDrive() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheets = ss.getSheets();
if (sheets === undefined || sheets.length === 0) {
return;
}
const now = new Date();
const csvBlobs = sheets.map((sheet) => {
const name = sheet.getName();
const csv = convertSheetToCsv(sheet);
Logger.log({ name, length: csv.length });
return Utilities.newBlob(csv, MimeType.CSV, `${name}.csv`)
});
const zipName = `export_${ss.getName()}_${now.toISOString()}.zip`;
const zip = Utilities.zip(csvBlobs, zipName);
DriveApp.createFile(zip);
}
function convertSheetToCsv(sheet) {
return sheet
.getDataRange()
.getValues()
.map((row) =>
row
.map((value) => value.toString())
.map((value) =>
value.includes("\n") || value.includes(",")
? '"' + value + '"'
: value
)
.join(",")
)
.join("\n");
}
and
// Menu.gs
function onOpen(e) {
const menu = SpreadsheetApp.getUi().createAddonMenu();
menu
.addItem('Export all sheets as CSV to Drive', 'exportSheetsToDrive')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}