I have created a template with several pages. After the template is used, I want to automatically remove any unused page.
This is how it works. I have created a certificate template with 25 pages with placeholders for name and other details. Each page is a certificate. After going through the data, the code below replaces the placeholders with data from the Google Sheets. Once that is completed - I want to remove all the extra pages in the document - for example: if only 5 template pages are modified, I want to remove the remaining 20 template pages from the document.
Any other improvement suggestions are welcome as this is my first App Script.
Addition to clarify the question:
The script takes data from a Google Sheet which has hundreds of rows of data. For example, if 5 certificates need to be created, the script gets all the data and loop and look for a certain flag (cert_data[i][6] == 1) to identify the rows of data that should be used for the certificate. Once the flag is found, the data in the row are stored in variables and is used to replace the place holders in the template file. Once the data in all flagged rows are replaced - for this example, only 5 template pages are replaced. Hence there will be a balance of 20 pages in the template that has not been used - I want to delete these pages.
function createDocument() {
//Setting ID for database
var SPREADSHEET_ID = "doc ID"
var spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
var worksheet = spreadsheet.getSheetByName("Sheet1");
var cert_data = worksheet.getDataRange().getValues();
//Setting template ID
var templateId = 'template ID goes here';
//Make a copy of the template file
var documentId = DriveApp.getFileById(templateId).makeCopy().getId();
//Get the document body as a variable
var body = DocumentApp.openById(documentId).getBody();
//Foramt date
var curDate = Utilities.formatDate(new Date(), "GMT+1", "dd MMMM yyyy")
var d = 0;
//Looping through all the data that is in the Google Sheet
for(var i = 1; i < cert_data.length ; i++){
var curdata = cert_data[i][6];
//Checking if the row data is to be used to create certificate
if (cert_data[i][6] == 1) {
var training_type = cert_data[i][12];
var hours = cert_data[i][9];
var user_name = cert_data[i][1];
var NIC = cert_data[i][3];
var date_completed = Utilities.formatDate(cert_data[i][8], "GMT+1", "dd MMMM yyyy");
var company = cert_data[i][2];
var cert_number = cert_data[i][0];
var date_now = curDate;
//Setting training names
if (training_type == "01G") {training_type = "Basic First Aid" + String.fromCharCode(10) + "& Automated External" + String.fromCharCode(10) + "Defibrillator Certificate"; var file_name = 'AED Training';}
if (training_type == "01B") {var file_name = 'Refresher Receipts';}
d++;
//Insert the data into the file
body.replaceText('{training_type' + d + '}', training_type);
body.replaceText('{hours' + d + '}', hours);
body.replaceText('{name' + d + '}', user_name);
body.replaceText('{NIC' + d + '}', NIC);
body.replaceText('{date_completed' + d + '}', date_completed);
body.replaceText('{company' + d + '}', company);
body.replaceText('{cert_numb' + d + '}', cert_number);
body.replaceText('{date_now' + d + '}', date_now);
}
}
//d is the number of pages used from the template file
//I want to delete all the balance pages (i.e. 25-d = x)
//Rename the copied file
DriveApp.getFileById(documentId).setName(file_name + ' - ' + company);
}
Each page of your Google Document is separated by the page breaks.
You want to delete several pages of Google Document from the last page in order.
Number of pages you want to delete is decided by your script.
If my understanding is correct, how about this sample script? The flow of this script is as follows.
Flow:
Retrieve paragraphs in the body of Document.
Retrieve elements in each paragraph. The page break is included in the paragraph.
Delete elements from last page in order.
When the number of page breaks is the same with deletePages, the script is stopped.
By this flow, several pages can be deleted from the last page in order.
Sample script:
Please copy&paste the following script and set the variables of deletePages and id. Then, run the script.
function myFunction() {
var deletePages = 3;
var id = "### documentId ###";
var paragraph = DocumentApp.openById(id)
.getBody()
.getParagraphs();
var counter = 0;
for (var i = paragraph.length - 1; i >= 0; i--) {
for (var j = 0; j < paragraph[i].getNumChildren(); j++)
if (
paragraph[i].getChild(j).getType() == DocumentApp.ElementType.PAGE_BREAK
)
counter++;
if (counter < deletePages)
paragraph[i].clear();
else if (counter == deletePages){
paragraph[i].getChild(paragraph[i].getNumChildren() - 1).removeFromParent();
break;
}
}
}
Note:
If you want to delete 5 pages from the last page, please set var deletePages = 5.
Also you can use this function like myFunction(deletePages) as a method. At that time, please remove var deletePages = 3;.
You can also use DocumentApp.getActiveDocument() instead of DocumentApp.openById(id).
From #derekantrican's suggestion, I updated the sample script.
References:
getParagraphs()
getChild()
clear()
Related
How do you extract comments from an MS Word doc that's been imported into Google Docs and put in a Google Sheet?
I've followed this suggestion to get REALLY close, including adding my own line for another column that shows WHO left the comment.
I am suspecting that MS Word native files that are imported into GDocs use some other convention for comments because there's a footer for those comments that says, "From Imported Document".
If this is true, then is there a solution for Word>GDocs imported documents with comments?
Here's my code but w/o my docID:
function listComments() {
// Change docId into your document's ID
// See below on how to
var docId = '';
var comments = Drive.Comments.list(docId);
var hList = [], cList = [], nList = [];
// Get list of comments
if (comments.items && comments.items.length > 0) {
for (var i = 0; i < comments.items.length; i++) {
var comment = comments.items[i];
// add comment and highlight to array's first element
hList.unshift([comment.context.value]);
cList.unshift([comment.content]);
nList.unshift([comment.author.displayName]);
}
// Set values to A and B
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange("A1:A" + nList.length).setValues(nList);
sheet.getRange("B1:B" + hList.length).setValues(hList);
sheet.getRange("C1:C" + cList.length).setValues(cList);
}
}
I have bug where when I run my send email function. its sending multiple emails instead of just one email notification here is my code what am I doing wrong??!?! I got 31 of the same emails. I believe the issue the for loop is sending an email each time the if statement is true instead of just one time if its true help.
here is my code:
function sendEmail(){
var ss = SpreadsheetApp.getActiveSpreadsheet(); //get active spreadsheet only! to get the url for the filter view
var SpreadsheetID = ss.getSheetId(); // get the sheet Id
var spreadsheetURL = ss.getUrl(); // get the current active sheet url
var SpreadsheetID = spreadsheetURL.split("/")[5]; // using the last / for getting the last parts of the email
var filterViewName = 'PO_Log Precentage'; // Name of the filter view you want to get the url from & MAKE SURE Title matches view name account for "spaces" too
var filterViewID = filterId(SpreadsheetID, filterViewName); // Getting filter view id
var url = createURL(spreadsheetURL, filterViewID); // creating the url to send the filter view id
Logger.log(url);// Testing to see the correct url is created
var po_numID = ss.getSheetByName("Purchase Orders List").getRange("A2").getDisplayValue().substr(0,3);// Gets the Purchase Order List Sheet and the PO# the first 3 Characters of the PO in A2
Logger.log(po_numID);
var email_va = ss.getSheetByName("Purchase Orders List");
//gonna build statuses to look for into array
var statusesToEmail = ['On-going', '']
//"Status" is in Column T (Col 2)
//"Precent" is in Column Q (Col 3)
var data = email_va.getDataRange().getValues()
// //var headerRowNumber = 1; // When checking for emails in the sheet you want to exclude the header/title row
var emailDataSheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/17G0QohHxjuAcZzwRtQ6AUW3aMTEvLnmTPs_USGcwvDA/edit#gid=1242890521").getSheetByName("TestA"); // Get The URL from another spreadsheet based on URL
Logger.log(emailDataSheet.getSheetName());
var emailData = emailDataSheet.getRange("A2:A").getDisplayValues().flat().map(po => po.substr(0,3));
Logger.log(emailData)///Working to get the first 3 charcters in column A
var subject = po_numID + " Po Log Daily Notification "; // Unique PoTitle of the email
var options = {} // Using the html body for the email
options.htmlBody = "Hi All, " + "The following" + '<a href=\"' +url+ '" > Purchase Orders </a>' + "are over 90% spent" + "";
for(var i = 0; i < data.length; i++){
let row = data[i];
if( statusesToEmail.includes(row[1]) & (row[2] >= .80)){
emailData.every((po, index) => {
if (po == po_numID){
const email = emailDataSheet.getRange(index + 2,7).getValue();//Getting the last colmun on the same row when the Po# are the same.
console.log(email);
MailApp.sendEmail(email, subject, '', options); // Sending the email which includes the url in options and sending it to the email address after making sure the first 3 Charcters Of the PO_log are the same as
return false;
} else {
return true;
}
});
}
}
}
here is the spreadsheet
https://docs.google.com/spreadsheets/d/1QW5PIGzy_NSh4MT3j_7PggxXq4XcW4dCKr4wKqIAp0E/edit#gid=611584429
you have to use the break function if u wish to stop the loop once the loop has been fulfiled, because if im not wrong , the email is sent if the IF condition is met , thus in the block that has mailapp.sendemail , you have to add in a break otherwise the loop will keep on happening. this is the basic of javascript and you should read up more about the FOR loop here
break as in just type "break" at the end of the code so the script will not continue to loop once the condition has been met.
I know what I'm doing is not good, and we're working on changing it for the better, but in the meantime I need to make do with what I have...
I have a rather large Google Sheet that more or less is acting as a DB. It's fed data from GForms and then that data is used on a number of other sheets for various purposes (e.g. reporting, selective info sharing to certain groups, etc.) mostly through querys and importranges.
The editing of the main sheet is restricted to only a handful of people, including myself. One of the others has made some changes (deleting some columns) that are going to break any queries or imports because the importrange won't catch the column changes and auto-update the range.
The changes made are actually good and worth keeping, so I don't want to revert them.
Is there a way for me to easily find out all of the sheets that use the main one so that I can update the affected formulas? In updating, I'll also need to find a way to avoid this happening again (I think I can do this by setting the range references using address? But that's future me's problem...)
Thanks in advance!
Find (Ctrl-H) gives the option to "Also search within formulae"
You can use this to search for references to a particular sheet eg searching "sheet1!" will find all cells that reference sheet1
Note this may not work if named ranges are defined, you may need to search for the named range as well.
To find dependencies between sheets in different documents (or even the same document) use the following script to search for references to a given doc id (eg within IMPORTRANGE) eg:
FindFormulae("1XgTuET_dJKP-i4JppvQQFI047-ZKP-i4o4bhE-K1lF-o")
function FindFormulae(keyword) {
// searches all google docs for formulae containing the given keyword
// outputs to sheet "output"
// may take a while if you have a lot of docs, and could timeout (5 minutes)
// if timeout just run the script again, it skips any docs already checked
// to cancel script execution go to https://script.google.com/home/executions
// Feb 2020 www.enex.net
var sheetOut= SpreadsheetApp.getActiveSpreadsheet().getSheetByName("output");
docsAlreadyChecked = sheetOut.getRange(1,1,sheetOut.getLastRow(),1).getValues();
var files = DriveApp.getFiles();
while (files.hasNext()) { // loop thru files
var file = files.next();
if (file.getMimeType() != "application/vnd.google-apps.spreadsheet") continue; // only process spreadsheets
docURL =file.getUrl();
docName =file.getName();
//Logger.log('Starting.. ' + docName + '\n' + docURL + '\n');
//DaysSinceUpdate = (new Date() - file.getLastUpdated())/(1000 * 60 * 60 * 24)
//if ( DaysSinceUpdate > 90 ) continue; // skip if file hasn't been updated for more than 7 days
if (docsAlreadyChecked.toString().indexOf(docName)>0) continue; // skip if already done // note this needs fixing as will fail if one doc is substring of another
var ssIn = SpreadsheetApp.openByUrl(docURL); // open doc
for (var i = 0; i < ssIn.getNumSheets(); i++) { //loop thru sheets in doc
sheetname = ssIn.getSheets()[i].getName();
//sheetid = ssIn.getSheets()[i].getSheetId();
if (ssIn.getSheets()[i].getMaxRows()==0) continue; //skip if no rows (eg if it's a chart)
ListFormulae(docURL, sheetname, keyword);
} // end for loop thru sheets
//strHyperlink = "=hyperlink(\"" + docURL + "#gid=" + ssIn.getSheets()[i].getSheetId() + "\", \"" + docName + "\")";
strHyperlink = "=hyperlink(\"" + docURL + "\", \"" + docName + "\")";
sheetOut.appendRow([strHyperlink, '', '', 'finished search ' + new Date() ]);
} // end while loop thru files
}
function ListFormulae(docURL, sheetname, keyword) {
var ssIn = SpreadsheetApp.openByUrl(docURL);
var sheetIn = ssIn.getSheetByName(sheetname);
//var sheetIn = ssIn.getSheetId(sheetid);
//var sheetname = ssIn.getSheetName();
var sheetOut= SpreadsheetApp.getActiveSpreadsheet().getSheetByName("output");
LastRow = sheetIn.getLastRow();
LastColumn = sheetIn.getLastColumn();
if (LastRow==0 && LastColumn==0) return; // nothing in sheet
var range = sheetIn.getRange(1, 1,LastRow , LastColumn);
var formulas = range.getFormulas();
for (var i in formulas) {
for (var j in formulas[i]) {
if (formulas[i][j]=="") continue; // skip if no formula
if (formulas[i][j].search(keyword) == -1) continue; //skip if keyword not found
strHyperlink = "=hyperlink(\"" + docURL + "#gid=" + sheetIn.getSheetId() + "\", \"" + ssIn.getName() + "\")";
rownum = parseInt(i)+1;
colnum = parseInt(j)+1;
sheetOut.appendRow([strHyperlink, sheetname, 'R' + rownum + 'C' + colnum, "'" + formulas[i][j]]);
}
}
}
In short: I'm new to Google App Script and as part of a larger project I want to be able to populate the options for a multiple choice item in a Google Form from information stored in a Google Sheet. The sheet is storing the name and contact information for several staff members where I work. I want the form to list the names of the individuals from the sheet and, based on that selection, access the other contact information to do additional work later on.
An example entry for the sheet could be (though the sheet should be able to contain any number of entries (obviously starting count on row 2, to ignore the header)):
LNAME FNAME ADDRESS PHONE
Smith John 123 Sesame Street (123) 456-7890
Piper Peter 12 Shore Lane (098) 765-4321
As for the form, I've populated it with initial items, the first of which is a multiple choice item where I want a different entry for each name in the Sheet (in this case I would want 2 entries that list "John Smith" and "Peter Piper").
It seems logical to put the code to load these names in the onload() function of the Form, as then the form will update each time it is used. As for the code, I initially tried the following:
function onOpen() {
// Get a handle for the form itself.
var form = FormApp.getActiveForm();
// Who is completing the form?
var swSheet = SpreadsheetApp.openByUrl("SHEET URL"));
var sheet = swSheet.getSheetByName("Staff");
var staff= form.getItems()[0];
var mcitem = staff.asMultipleChoiceItem();
mcitem.setChoices([
mcitem.createChoice(
mcitem.createChoice(sheet.getRange(2, 2) + " " + sheet.getRange(2, 3)),
mcitem.createChoice(sheet.getRange(3, 2) + " " + sheet.getRange(3, 3)),
mcitem.createChoice(sheet.getRange(4, 2) + " " + sheet.getRange(4, 3))
]);
}
The problem with this is that I get an error with the openByUrl() call (an online search shows that this was deprecated for security reasons). There is an IMPORTRANGE() function that could pull the correct information for me, but this appears to only be accessible from WITHIN a Sheet, not in Google App Script. I looked around online as well and can't seem to find any other ideas that I could consider.
Note: I realize that this is hard coded for the first 3 entries, and not an unlimited number, but that is the end goal. At present I'm just trying to figure out how to pull information from a Sheet into Google App Script.
You can deploy your script as a Web App and send to the form recipients a Web App URL instead of the form link. The form will be automatically updated every time a user opens the Web App URL.
// will be automatically run every time the user opens the URL of the web deployment
function doGet(){
var form = FormApp.openById("FORM ID");
var swSheet = SpreadsheetApp.openByUrl("SHEET URL"));
var sheet = swSheet.getSheetByName("Staff");
var staff= form.getItems()[0];
var mcitem = staff.asMultipleChoiceItem();
mcitem.setChoices([
mcitem.createChoice(sheet.getRange(2, 2) + " " + sheet.getRange(2, 3)),
mcitem.createChoice(sheet.getRange(3, 2) + " " + sheet.getRange(3, 3)),
mcitem.createChoice(sheet.getRange(4, 2) + " " + sheet.getRange(4, 3))
]);
// get the link to the form
var URL=form.getPublishedUrl();
// redirect to the prefilled form URL dynamically created above
return HtmlService.createHtmlOutput("<script>window.top.location.href=\"" + URL + "\"</script>");
}
Here is my suggestion for this. It works for me; takes values from a range for Rows and Columns from a Google sheet and places them in Google Form Multiple Choice Grid questions:
var form = FormApp.openById('MyForm');
var PtjGridList = form.getItemById(MyFormItemId).asGridItem();
var ss = SpreadsheetApp.openById("MyGoogleSheet");
var PtjNombre = ss.getSheetByName("MyDataSheet");
var RowValues = PtjNombre.getRange(2, 1, PtjNombre.getLastRow() - 1).getValues();
var ValuesRow = [];
for(var i = 0; i < RowValues.length; i++)
if(RowValues[i][0] != "")
ValuesRow[i] = RowValues[i][0];
PtjGridList.setRows(ValuesRow)
var ColumnValues = PtjNombre.getRange(2, 2, PtjNombre.getLastRow() - 1).getValues();
var ValuesColumn = [];
for(var i = 0; i < ColumnValues.length; i++)
if(ColumnValues[i][0] != "")
ValuesColumn[i] = ColumnValues[i][0];
PtjGridList.setColumns(ValuesColumn)
I am trying to find a script, or begin writing one, that takes a simple Google Form with a drop-down list of names (i.e. Tom, Jane) and a text area, and inputs both the date and the text into columns based on the selected name (i.e. Tom Date, Tom Comment). This is so I can make a quick entry feedback form for leaving individualized, date-based feedback for students, which they can then access later.
I looked through the GAS documentation and looked for examples, but as I am a novice, I really didn't know where to begin.
Any ideas on how to do this?
I think I did something similar but mine is for admins to observe teachers. I'm just learning as well, so I'm sure there are better ways to do this but it works. I definitely should have broken it up into more functions.
My form has a trigger to fire the onClose() when submitted. onClose() produces a Google Doc by reading the spreadsheet containing the form data in a nice format that the observer can then edit and share with the teacher. I wanted the Google Doc produced to have the name of the teacher being observed in the file name and I wanted it shared with the admin who did the observing.
The fact that some of the fields are dropdowns doesn't matter, it is just an itemResponse from the list of all responses.
function onClose()
{
var form = FormApp.openById(' whatever your form id is');
//the spreadsheet of the form responses
var formResponses = form.getResponses();
var d = new Date();
var currentTime = d.toLocaleTimeString();
var date = d.toLocaleDateString();
//Need to get the name of the teacher being observed
var formResponse = formResponses[formResponses.length-1];
var itemResponses = formResponse.getItemResponses();
var itemResponse = itemResponses[0]; //the teacher name dropdown box
var teacherName = itemResponse.getResponse() + '-' + itemResponses[1].getResponse();
//create the new document
var fileName = 'Observation-'+ teacherName + '-' + date + '-' + currentTime;
var doc = DocumentApp.create(fileName);
var activeDoc = DocumentApp.getActiveDocument();
var files = DriveApp.getFilesByName(fileName);
while (files.hasNext()) {
var file = files.next();
if (file.getName().equals(fileName))
{
//this is the last item on my form the name of the observer
var itemResponse21 = itemResponses[21];
var observer = itemResponse21.getResponse();
// Logger.log('Person to share with is ' + observer);
// share this google doc with the observer
file.addEditor(observer);
}
}
//ommittd a bunch of styles
//This would get all forms submitted, but I only need the last one
// so I just set the loop to get the last form submitted.
//leaving for loop just so I remember I can go through all forms again
//if I want to.
for (var i = formResponses.length-1; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
//get the individual responses within the form.addCheckboxItem()
for (var j = 0; j < itemResponses.length; j++) {
//pull the first item out again (even though I did for file name)
var itemResponse = itemResponses[j]; //teacher name from a dropbox
var itemResponse2 = itemResponses[j+1]; //date
var itemResponse3 = itemResponses[j+2]; //time
if (j == 0) //the first field on the form
{
//put the headings in
par3 = doc.appendParagraph(' SCHOOL NAME');
par3 = doc.appendParagraph('WALK IN OBSERVATION');
par3 = doc.appendParagraph('2013-2014');
//THIS is the teacher being observed and the date and time --- all on same line
var headingLine = itemResponse.getItem().getTitle() + '\t\t' + itemResponse2.getItem().getTitle() + ' / ' + itemResponse3.getItem().getTitle();
par1 = doc.appendParagraph(headingLine);
var answerLine = itemResponse.getResponse() + '\t\t\t\t\t' + itemResponse2.getResponse() + ' / ' + itemResponse3.getResponse();
par2 = doc.appendParagraph(answerLine);
j++; //do this to skip over date and time
j++;
} //end of j = 0;
else
// then I have a bunch of if statements for some of the
// specific fields I need to do something special with.
// After the last if, I just have an else to handle all other
// form responses that I don't do anything special for other than display.
//my last else is:
else
//THIS ELSE IS HANDLING ALL NON CHECK BOXES AND JUST DISPLAYING THE TITLE IN BOLD FONT, THE COMMENTS IN REGULAR FONT
{
par1 = doc.appendParagraph(itemResponse.getItem().getTitle());
par1.setAttributes(style);
par2 = doc.appendParagraph( itemResponse.getResponse());
par2.setAttributes(style);
} //END OF ELSE
} //end of for going through each cell in a row of the repsonses
} //end of for going through each row -- only had it set to do the very last row