Add linebreaks in Google forms grids & title - google-apps-script

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).

Related

Google Apps Script Mail Merge - Grabbing Entire Body

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

Getting SubLabels in App Script Function from Gmail

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))
}

search slide and get page element text

My script searches one specific slide in a presentation. Then it gets the text from the first page element. This page element is a shape with only one word. After this, when I put strings before and after this page element text, there is a break in the text.
function readShapeText() {
var presentation = SlidesApp.getActivePresentation();
var slides = presentation.getSlides();
for (i = 0; i < slides.length; i++) {
if(slides[i].getObjectId() == 'mySlideId'){
var pageElement = slides[i].getPageElements()[0].asShape().getText().asString();
}
}
var myModifiedElement = "My_" + pageElement + "_is_cool";
}
The output is with a break, but I need in one line:
My_TestElement
_is_cool
How can I eliminate or suppress the break? And is there a better way to find a specific slide without using "for loop" f.e. like presentation.openSlideById(xxxxxx)?
How about these answers?
Q1: How can I eliminate or suppress the break?
It seems that the end of texts is always given \n. This can be also seen from values retrieved by Slides.Presentations.get(). So if you want to retrieve the values without \n, you can do it using replace("\n", "").
Q2: Is there a better way to find a specific slide without using "for loop" f.e. like presentation.openSlideById(xxxxxx)?
How about the following sample script? It retrieved the specific slide using filter(), because the key of objectId is included in the array. And replace("\n", "") was also used.
function readShapeText() {
var mySlideId = "mySlideId"; // Please input this.
var presentation = SlidesApp.getActivePresentation();
var slides = presentation.getSlides();
var slide = slides.filter(function(e){return e.getObjectId() == mySlideId})[0];
var pageElement = slide.getPageElements()[0].asShape().getText().asString().replace("\n", "");
var myModifiedElement = "My_" + pageElement + "_is_cool";
Logger.log(myModifiedElement)
}
If I misunderstand your question, I'm sorry.

Set the Glyph Type to "Checkbox" in Google Apps Script

I'm adding list items to a Google Doc. I know GlyphType lets you set the bullet type:
var myArray = myObjects[i].myColumn.split(", ");
for (var i = 0; i < myArray.length; i++) {
body.appendListItem(myArray[i])
.setGlyphType(DocumentApp.GlyphType.BULLET)
.setLineSpacing(1.85)
.setIndentStart(40);
}
body.appendListItem("Text").setIndentStart(40);
But how can I set the bullet type to "checkbox"? It is one of the available options within GDocs:
http://www.ultraimg.com/images/ScreenShot.png
I suspect if I were editing an existing document with the glyph type already set, .appendListItem() wouldn't change the glyph type. But my project involves creating a GDoc from scratch and doesn't lend itself well to using a template (because the number of times the template text is used would need to be variable).
Unfortunately It seems to be not possible... below is a small test I tried on a doc with "square bullets" :
function myFunction() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var element = body.getChild(1).asListItem();
var attrs = element.getAttributes();
for (var att in attrs) {
Logger.log(att + " : " + attrs[att]);
}
}
And the result : they show up as "normal" bullets.

Setting setAttribute() for line formatting, line_spacing, SPACING_AFTER, SPACING_BEFORE

I am trying to generate a report in a Google doc from a template file. When it copies the document it resets all of the formatting to the defaulted for the user and not what the format in the original doc is. I've tried the following to try and set the formatting on a both the document, tableRow and tableCell level though when the report is created the line spacing is 1.5 and there is a space after paragraph
var style = {};
style[DocumentApp.Attribute.SPACING_AFTER] =0;
style[DocumentApp.Attribute.SPACING_BEFORE] =0;
style[DocumentApp.Attribute.LINE_SPACING]=1;
var newrow= tables[2].insertTableRow(n).setBold(false).setAttributes(style);
if(j==0){
newrow.insertTableCell(0,reportDate).setPaddingBottom(0).setPaddingTop(0).setAttributes(style);
}
else{
newrow.insertTableCell(0,'').setPaddingBottom(0).setPaddingTop(0).setAttributes(style);
}
newrow.insertTableCell(0,values1[rowId1][1]+' '+values1[rowId1][2]).setPaddingBottom(0).setPaddingTop(0).setAttributes(style);
newrow.insertTableCell(0,'').setPaddingBottom(0).setPaddingTop(0).setAttributes(style);
doc.editAsText().setAttributes(style);
any suggestions on how to have the report follow these attributes?
I believe the SPACING_AFTER, SPACING_BEFORE & LINE_SPACING are not attributes associated with the TABLE_CELL object. You must reference the child PARAGRAPH in order to set these.
var style = {};
style[DocumentApp.Attribute.SPACING_AFTER] =0;
style[DocumentApp.Attribute.SPACING_BEFORE] =0;
style[DocumentApp.Attribute.LINE_SPACING]=1;
var newrow = tables[2]
.insertTableRow(n)
.setBold(false);
if (j == 0) {
newrow.insertTableCell(0,reportDate)
.setPaddingBottom(0)
.setPaddingTop(0);
}
else {
newrow.insertTableCell(0,'').setPaddingBottom(0).setPaddingTop(0);
}
newrow.insertTableCell(0,values1[rowId1][1]+' '+values1[rowId1][2])
.setPaddingBottom(0)
.setPaddingTop(0);
newrow.insertTableCell(0,'')
.setPaddingBottom(0)
.setPaddingTop(0);
var newrowTableCell = newrow.getChild(0);
var newrowParagraph = newrowTableCell.getChild(0).setAttributes(style);
As the set attributes was not setting the attributes and you can not cast any of the elements as paragraphs I resolved this problem by getting all of the paragraphs and setting them manually by adding this at the end
var p=doc.getParagraphs();
for(i=0;i<p.length; i++){
p[i].setLineSpacing(1).setSpacingAfter(0);
Do the users need to be able to edit the report, or just view it? Why not generate a pdf? Every time I've done it, it saves the formatting.
var file = DocsList.getFileById('1DIfn_wVpXSI4hU5zG43Fvp2ZdpUP_KqgtgFRT9NWJ7E');
var newFile = DocsList.copy(file, ename+'-'+reportDate+'Monthly Report');
var report=DocsList.createFile(newFile.getAs("application/pdf")).rename(newFile.getName() + ".pdf");
var a = report.getID();
var doc = DocumentApp.openById(a);