Here the problem:
I have three different categories and I wanted to group them in a unique label with three sublables. Then, i want to pick those sublabels and run my script.
Here the actual situation in Gmail:
as is situation
I want to put all those labels under a main lable nesting them like this:
nested labels
Now, nesting lables in Gmail il very easy but the main problem is when you run a script based on nested labels. Here my function:
function getGmailEmails(){
var label = GmailApp.getUserLabelByName("UtentiSW");
var threads = label.getThreads();
for(var i = threads.length - 1; i >=0; i--){
var messages = threads[i].getMessages();
for (var j = 0; j <messages.length; j++){
var message = messages[j];
if (message.isUnread()){
extractDetails(message);
GmailApp.markMessageRead(message);
}
}
//threads[i].removeLabel(label); //delete the label after getting the message
}
}
If i try to create a nested label and pick it in the function using the name of the label as shown, does't work. It works only if i put the label in the root folder of gmail as per first picture.
Anyone can help? Thanks
Creating Labels and Sub Labels
Create Label before Sub Labels
function createLabelsAndSubLabel() {
const labels = ["Q1","Q1/1","Q1/2","Q1/3"]
labels.forEach(l => GmailApp.createLabel(l))
}
I've never tried it for more that 4 levels but if you want to extend it you can, just remember to create the labels before the sublabel or they all become separate labels and you have to go delete them and start all over again.
What it looks like in Gmail:
Sorry my back is an image
Listing all of your labels in a Spreadsheet
function listAllLabels() {
let labels = JSON.parse(Gmail.Users.Labels.list("me")).labels;
let hdr = ["Name","Vis","Type","id"]
let o = labels.map(obj => [obj.name,obj.messageListVisibility,obj.type,obj.id]);
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
sh.clearContents();
o.unshift(hdr);
sh.getRange(1,1,o.length,o[0].length).setValues(o).sort({column:1,ascending:true});
}
Gmail API needs to be enabled
In regard to your last comment I added this code to create sub labels to an existing label
function createLabelsAndSubLabel() {
const labels = ["Q1/3/1","Q1/3/2","Q1/3/3"]
labels.forEach(l => GmailApp.createLabel(l))
}
Related
I have a code written to validate some blank cells and display a toast with the label names which are not filled, Since the label names are lengthy and too many, the toast opo is not able to show the names of all labels as below -
My script is as below-
function validation() {
var sheet = SpreadsheetApp.getActive();
var rg1= sheet.getRange('B9');
var rg2 = sheet.getRange('D9')
var rg3 = sheet.getRange('F9')
var rg4 = sheet.getRange('H9')
var rg5 = sheet.getRange('B13')
var rg6 = sheet.getRange('D13')
var rg7 = sheet.getRange('B26')
var rg8 = sheet.getRange('D26')
var rg9 = sheet.getRange('B30')
var ranges = [rg1, rg2, rg3,rg4,rg5,rg6,rg7,rg8,rg9];
var unfilled = [];
for(var range of ranges) {
if(range.isBlank()) {
unfilled.push(range.offset(-1,0).getValue())
range.setBackground('red');
} else {range.setBackground(null)};
}
if(unfilled.length == 0) SpreadsheetApp.getActive().toast("All filled", "Thank you",10);
else {
unfilled = unfilled.join(", ");
SpreadsheetApp.getActive().toast("Please fill - " + unfilled, "⚠️ Unfilled data",10);
}
}
Can I increase the size of the toast? or use use two toasts to break the info and display, or any other way I can show the names of the fields.
Please help!
As suggested by the comments it would be important to leave an answer for the future.
The documentation on how the API works or features in general using Sheet it is not possible to add size or adjust the way the toast is presented or variables that you can add, for example:
toast(msg, title, timeoutSeconds)
The second suggestion also works great as it shows a bigger alert and you can add buttons to it, example:
This could be achieved by utilizing a sample code like:
function alertMessageOKButton() {
var result = SpreadsheetApp.getUi().alert("Alert message", SpreadsheetApp.getUi().ButtonSet.OK);
SpreadsheetApp.getActive().toast(result);
}
You can also share your feedback or request the feature for the future here:
https://developers.google.com/apps-script/support
References:
Alert Messages in Google Sheets
Toast Sheets
I am officially stuck! Hopefully a fresh set of eyes can help...
I can't figure out out to grab the entire body of my source template and place it in one shot on the target document for reception of the data. As you can see from my code below, my workaround (and literally only thing I stumbled upon that worked) was to grab each line of the template document, and then place each line one-by-one on the target document. However, I don't consider this the appropriate solution for a few reasons: it's not pretty, it's a more resource-expensive run, and it absolutely would not work if I was creating a letter.
Thankfully, since this was envelopes, I got through the job, but I'd like to discover the correct solution before my next mailing. I poured through the documentation, and there were a few functions that were potential candidates (such as 'getBody') but seemed not to be available (I would get 'not a function' errors. So, I'm at a loss.
Another issue with getBody(): it seems to only send plain-text forward. It does not retain any formatting or fonts I arranged in my template.
So my objectives are:
1. Grab the rich-text content of my template document
2. With each loop iteration, apply the content to the next page of target document in one-shot (not line by line).
3. Have this content maintain the formatting (font sizes, fonts, tabbing, spacing, etc.) of my template.
4. Update the dynamic fields with the row of information it's on for that iteration and move on.
I would greatly appreciate any help and/or insight!
Thanks!
function envelopeMailMerge() {
var sourceID = "[id of data sheet]";
var rangeData = 'OnePerFamily!A2:E251';
var values = Sheets.Spreadsheets.Values.get(sourceID,rangeData).values;
var templateID = "[id of template document]";
var targetID = "[id of target document]";
var templateBody = DocumentApp.openById(templateID).getBody();
var targetBody = DocumentApp.openById(targetID).getBody();
//obviously what follows is a ridiculous way to do this, hence my issue
var theContent = templateBody.getChild(0).copy();
var theContent2 = templateBody.getChild(1).copy();
var theContent3 = templateBody.getChild(2).copy();
var theContent4 = templateBody.getChild(3).copy();
var theContent5 = templateBody.getChild(4).copy();
var theContent6 = templateBody.getChild(5).copy();
var theContent7 = templateBody.getChild(6).copy();
var theContent8 = templateBody.getChild(7).copy();
var theContent9 = templateBody.getChild(8).copy();
var theContent10 = templateBody.getChild(9).copy();
var theContent11 = templateBody.getChild(10).copy();
var theContent12 = templateBody.getChild(11).copy();
var theContent13 = templateBody.getChild(12).copy();
var theContent14 = templateBody.getChild(13).copy();
var theContent15 = templateBody.getChild(14).copy();
var theContent16 = templateBody.getChild(15).copy();
var theContent17 = templateBody.getChild(16).copy();
//Clear the target document before creating the new merge
targetBody.clear();
if (!values) {
Logger.log('No data found...');
} else {
for (var row=0; row < values.length; row++) {
var name = values[row][0];
var address = values[row][1];
var city = values[row][2];
var state = values[row][3];
var zip = values[row][4];
//Again, what follows is ridiculous and not an ideal solution
targetBody.appendParagraph(theContent.copy());
targetBody.appendParagraph(theContent2.copy());
targetBody.appendParagraph(theContent3.copy());
targetBody.appendParagraph(theContent4.copy());
targetBody.appendParagraph(theContent5.copy());
targetBody.appendParagraph(theContent6.copy());
targetBody.appendParagraph(theContent7.copy());
targetBody.appendParagraph(theContent8.copy());
targetBody.appendParagraph(theContent9.copy());
targetBody.appendParagraph(theContent10.copy());
targetBody.appendParagraph(theContent11.copy());
targetBody.appendParagraph(theContent12.copy());
targetBody.appendParagraph(theContent13.copy());
targetBody.appendParagraph(theContent14.copy());
targetBody.appendParagraph(theContent15.copy());
targetBody.appendParagraph(theContent16.copy());
targetBody.appendParagraph(theContent17.copy());
//Update the dynamic fields with this row's data
targetBody.replaceText('{{Name}}',name);
targetBody.replaceText('{{Address}}',address);
targetBody.replaceText('{{City}}',city);
targetBody.replaceText('{{ST}}',state);
targetBody.replaceText('{{ZIP}}',zip);
//Insert page break so next iteration begins on new page
targetBody.appendPageBreak();
}
}
}
In the following example I am using a more Javascript approach using String.prototype.replace() to replace the text. I consider the following:
You have a template DOC where you have some strings like these {{Name}}:
You have a spreadsheet where the data to replace the template lives
You want to create a Google Doc for every of the rows
Considering this as true, the example shows this approach:
Grab all the text from the template doc
Replace the text using String.prototype.replace()
Setting the text of the new doc with the replaced one
Code.gs
const templateDocID = "<Template_DOC_ID>"
const dataSsId = "<Data_SS_ID>"
const doC = DocumentApp.openById(templateDocID)
const sS = SpreadsheetApp.openById(dataSsId).getSheets()[0]
function createDocFromTemplate() {
/* Grab the data from the sheets */
const dataToReplace = sS.getRange('A2:E').getValues().filter(n => n[0] !== "")
dataToReplace.forEach((data) => {
let body = doC.getBody().getText()
/* Create a new doc for each row */
const newDocument = DocumentApp.create('New Document')
/* A quick approach to extract the data */
const [name, address, city, state, zip] = data
/* Using string.replace() */
body = body.replace("{{Name}}", name)
body = body.replace('{{Address}}', address)
body = body.replace("{{City}}", city)
body = body.replace("{{ST}}", state)
body = body.replace("{{ZIP}}", zip)
/* Setting the text */
newDocument.getBody().setText(body)
/* Or sending it as an email */
GmailApp.sendEmail('email#gmail.com', 'From Template', body)
Logger.log(newDocument.getUrl())
})
}
This is an example that can help you, but you can adapt it to meet your needs.
Documentation
SpreadsheetApp
GmailApp
Optimize the replace function
I would like to use a list in a spreadsheet to create multiple forms for different classes. I have the simple code to build a new form for each class, but I would like to add some form elements to each of the new forms. I know how to build a single form with the elements I want from a script, and I know how to build a bunch of empty forms with a script pulling the form names from the sheet. But I am struggling with the syntax on the forEach section of this script to add the form items to each new form.
function createForms() {
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet5');
const range = ss.getDataRange().getValues();
range.forEach(item => FormApp.create(item));
This snippet of code creates a new form for each item in sheet 5. Now when this iterates through these new forms, I want to add some items to each form. I am not sure how to get the forEach statement to iterate through the forms and add items to each one.
Any help will be appreciated.
I have tried this but it does not work.
function createForms() {
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet5');
const range = ss.getDataRange().getValues();
range.forEach(item => FormApp.create(item))
const form = FormApp.create(item);
form.addTextItem()
.setTitle('Name')
}
After the answer I received (THANK YOU) I have modified the script and it is working to create the forms as shown from the list in the spreadsheet. Lastly, I would like to have the forms created in a parent folder with a new folder for each form.
function createForms() {
//use this to force email text box to be email address
const emailValidation = FormApp.createTextValidation()
.setHelpText('Input must be email address.')
.requireTextIsEmail()
.build();
const parentFolder = DriveApp.getFolderById('PUT YOUR FOLDER ID HERE');
let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('courses');
let values = ss.getDataRange().getValues();
for (let i = 0; i < values.length; i++) {
let form = FormApp.create(values[i]);
form.addTextItem().setTitle("Name").setHelpText('Please enter your full name').setRequired(true);
form.addTextItem().setTitle("Email Address").setRequired(true).setValidation(emailValidation).setHelpText('An email address is required');
form.addParagraphTextItem().setTitle('Details').setHelpText('Please provide the details you need to explain').setRequired(true);
form.addMultipleChoiceItem().setTitle("Day Choice").setHelpText('Choose the best day for you').setRequired(true)
.setChoiceValues(['Monday', 'Tuesday','Wednesday','Thursday','Friday']);
}
}
The main issue here is in the way you are retrieving the list for the form names.
The getDataRange method will return the range corresponding to the dimensions in which data is present. However, the getValues ends up returning a two-dimensional array of type [][].
While the syntax of the forEach is correct, you are essentially only traversing the array in an unidimensional manner.
It is also important to note the fact that when adding an item to a form, you will have to declare the item of the type you want the form element to be.
Therefore, assuming that the spreadsheet looks like the one below and you want each one form to contain 3 elements, this is how the script should look like:
Spreadsheet
Script
function createForms() {
let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet5');
let values = ss.getDataRange().getValues();
for (let i = 0; i < values.length; i++) {
for (let j = 0; j < values[i].length; j++) {
let form = FormApp.create(values[i][j]);
form.addTextItem().setTitle("Text Item " + values[i][j]);
form.addGridItem().setTitle("Grid Item " + values[i][j]);
form.addMultipleChoiceItem().setTitle("Multiple Choice " + values[i][j]);
}
}
}
Note
I suggest you start by using a simple for loop if you have doubts related to how the forEach works.
However, you can easily switch from for to forEach in the code above.
Reference
getDataRange();
getValues();
create(title).
I have been able with the help from ale13 to figure out what I was wanting to do. Here is the final script. It will create a new form, a new folder in the parent folder for each line of data in the sheet that you specify. It is designed for a single column of names, to create a separate form for each class or group and organize them into new folders in a parent folder. The form types and questions can be modified in the script.
/* This script will create a new form for each line in column A of the sheet by name */
function createForms() {
//use this to force email text box to be email address
const emailValidation = FormApp.createTextValidation()
.setHelpText('Input must be email address.')
.requireTextIsEmail()
.build();
//we will want to save these new forms into a parent google drive folder
//next we will want to create a new folder for each course in this parent folder
// and save the new forms in these child folders with the name of the course
const parentFolder = DriveApp.getFolderById('PUT YOUR FOLDER ID HERE');
let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PUT YOUR SHEET NAME HERE');
let values = ss.getDataRange().getValues();
for (let i = 0; i < values.length; i++) {
let folder = parentFolder.createFolder(values[i]);
let form = FormApp.create(values[i]);
form.addTextItem().setTitle("Name").setHelpText('Please enter your full name').setRequired(true);
form.addTextItem().setTitle("Email Address").setRequired(true).setValidation(emailValidation).setHelpText('An email address is required');
form.addParagraphTextItem().setTitle('Details').setHelpText('Please provide the details you need to explain').setRequired(true);
form.addMultipleChoiceItem().setTitle("Day Choice").setHelpText('Choose the best day for you').setRequired(true)
.setChoiceValues(['Monday', 'Tuesday','Wednesday','Thursday','Friday']);
form.addListItem().setTitle('Rooms').setHelpText('Select your room')
.setChoiceValues(['Room A','Room B','Room C']);
let formFile = DriveApp.getFileById(form.getId());
formFile.moveTo(folder);
}
}
I'm currently using the following Google script to add line breaks in questions:
function addLineBreaks() {
var form = FormApp.getActiveForm();
var multiplechoices = form.getItems(FormApp.ItemType.MULTIPLE_CHOICE);
var date = form.getItems(FormApp.ItemType.DATE);
var textbox = form.getItems(FormApp.ItemType.TEXT);
var checkboxgrid = form.getItems(FormApp.ItemType.GRID)
questions = multiplechoices.concat(date, textbox, checkboxgrid);
for(i = 0; i < questions.length; i++) {
var title = questions[i].getTitle();
if(title.indexOf("\n") < 0) {
questions[i].setTitle(title.replace(" | ", "\n"));
}
}
}
My problem is that I cannot add line breaks to grid rows and general titles (Tt). I'm confident that it's because the script I'm using refers to getTitle, but couldn't get it to work with getRows or setRows.
I don't mind having to add "|" and have it replaced later with a line break when the script runs, but I do need to find a solution regarding the grids (multiple choice grids rows only).
I want to make a filter that is native to gmail for certain set of parameters.
Basically I use the google alias function a lot (the + sign after your email). I want to automate the process of creating a filter that reads the "To" line then looks for a "+". If the a "+" is found it will make a label of what is after the "+". Then it will create a dedicated/native filter that will: skip the inbox and apply the label of what is after the "+".
I have looked through the gmail scripts and have not found a way to make a native filter. I understand that this functionality might have just been implemented.
function getTo() {
// Log the To line of up to the first 50 emails in your Inbox
var email = Session.getActiveUser().getEmail();
Logger.log(email);
var threads = GmailApp.getInboxThreads(0, 50);
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
for (var j = 0; j < messages.length; j++) {
Logger.log(messages[j].getTo()||email);
}
}
}
Any help would be great.
Solution:
// Creates a filter to put all email from ${toAddress} into
// Gmail label ${labelName}
function createToFilter(toAddress, labelName) {
// Lists all the filters for the user running the script, 'me'
var labels = Gmail.Users.Settings.Filters.list('me')
// Search through the existing filters for ${toAddress}
var label = true
labels.filter.forEach(function(l) {
if (l.criteria.to === toAddress) {
label = null
}
})
// If the filter does exist, return
if (label === null) return
else {
// Create the new label
GmailApp.createLabel(labelName)
// Lists all the labels for the user running the script, 'me'
var labelids = Gmail.Users.Labels.list('me')
// Search through the existing labels for ${labelName}
// this operation is still needed to get the label ID
var labelid = false
labelids.labels.forEach(function(a) {
if (a.name === labelName) {
labelid = a
}
})
Logger.log(labelid);
Logger.log(labelid.id);
// Create a new filter object (really just POD)
var filter = Gmail.newFilter()
// Make the filter activate when the to address is ${toAddress}
filter.criteria = Gmail.newFilterCriteria()
filter.criteria.to = toAddress
// Make the filter remove the label id of ${"INBOX"}
filter.action = Gmail.newFilterAction()
filter.action.removeLabelIds = ["INBOX"];
// Make the filter apply the label id of ${labelName}
filter.action.addLabelIds = [labelid.id];
// Add the filter to the user's ('me') settings
Gmail.Users.Settings.Filters.create(filter, 'me')
}
}
Thanks, Ender
The functionality seems to exist, but requires enabling the full Gmail API.
For your Apps script, follow the directions at https://developers.google.com/apps-script/guides/services/advanced to enable Advanced Google Services.
When I first tried using the Gmail.Users.Settings.Filters.list('me') call I got an authorization error which gave me a link to enable the Gmail API in the Developer Console which just amounted to flipping a switch.
Once you have this enabled, you can write a script to make filters, like
//
// Creates a filter to put all email from ${toAddress} into
// Gmail label ${labelName}
//
function createToFilter (toAddress, labelName) {
// Lists all the labels for the user running the script, 'me'
var labels = Gmail.Users.Labels.list('me')
// Search through the existing labels for ${labelName}
var label = null
labels.labels.forEach(function (l) {
if (l.name === labelName) {
label = l
}
})
// If the label doesn't exist, return
if (label === null) return
// Create a new filter object (really just POD)
var filter = Gmail.newFilter()
// Make the filter activate when the to address is ${toAddress}
filter.criteria = Gmail.newFilterCriteria()
filter.criteria.to = toAddress
// Make the filter apply the label id of ${labelName}
filter.action = Gmail.newFilterAction()
filter.action.addLabelIds = [label.id]
// Add the filter to the user's ('me') settings
Gmail.Users.Settings.Filters.create(filter, 'me')
}
function main () {
createToFilter('youruser+foo#gmail.com', 'Aliases/foo')
}
I wasn't able to figure out how to create a filter that retroactively labels messages automatically since Google doesn't seem to expose that in their API.