Create PDF copy of doc App script - google-apps-script

I am attempting to create a PDF based on the entries in a spreadsheet with the basic flow of the following.
Fill in spreadsheet
Fire off the submit from a menu button (pop up for asking for Job Number)
Templated Doc gets a copy made and cell contents appended to a table in the copy doc.
Copy doc gets saved as a PDF in a specific folder.
copy of doc gets deleted.
I am just trying to get the process of getting the PDF created in the specified folder. It was working prior to my creating a copy of the Doc and just using the template so I am not sure why it will not work now. Any input would be appreciated as well as any info on how to append the cell data from the sheet into the copy of the doc. Newby to be sure so any help would be appreciated. Code attached
function createDoc () {
var job = Browser.inputBox('Enter Job Number', 'Job Number', Browser.Buttons.OK);
var dtStr = Utilities.formatDate(new Date(), "GMT", "MMddyy")
// create temp file before edited with spreadsheet data
var tmpName = "tmpname"
var folder = DriveApp.getFolderById('1C_k3MvoT33WhSXVNMmFQNFhqaW8')
var tmpl = DriveApp.getFileById('225xZAECq0rkdJnsr4k9VjL91B7vgJh8Y- t9YrsbCEgc').makeCopy(tmpName).getID();
// get document and make PDF in folder
var doc = DriveApp.getFileByID(tmpl).getAs("application/pdf");
var pdf = doc.setName(job +"-"+dtStr+".pdf");
folder.createFile(pdf)
}

I only see some typos in some functions names, remember Google Apps Script is a scripting language based on JavaScript, so:
JavaScript is a case-sensitive language. This means that language
keywords, variables, function names, and any other identifiers must
always be typed with a consistent capitalization of letters.
On this line:
var tmpl = DriveApp.getFileById('').makeCopy(tmpName).getID();
.getID() the D must be lowercase, just change it to .getId()
On this line:
doc = DriveApp.getFileByID(tmpl).getAs("application/pdf");
.getFileByID() the D must also be lowercase, just change it to .getFileById()
To delete the temporary document, you can use the removeFile() but first you need to get the file, not just the id, so I recommend before getting the id of the copy, you get the file and then get the file's id, like this:
var blob = DriveApp.getFileById('yourId').makeCopy(tmpName)
var tmpl = blob.getId();
Then after the creation of the pdf, you can delete with this:
folder.removeFile(blob);
To create Custom Menus, the official documentation has some good examples.
EDIT:
This is an example to append a table to a Google Doc that can get you started, the cell variable you can change it to the range of data of your Spreadsheet:
function appendTable(){
var document = DocumentApp.openById('docId');
var body = document.getBody();
var cells = [
['Row 1, Cell 1', 'Row 1, Cell 2'],
['Row 2, Cell 1', 'Row 2, Cell 2']
];
body.appendTable(cells);
document.saveAndClose();
}

I have included the simple of the code that I use for this same purpose. This includes a checker that denotes where the merge code is in the process. This is helpful when processing many rows at a time. It also provides the url of the created file. The way that I use this is with Google Forms where the data is sent to "Form Responses" and is then sent to "Merge Data" for continued functions using =QUERY().
This code leaves you with both the Google Doc and the final .pdf file. If you would like to have only the .pdf, simply repeat the .setTrashed() method on the Google Doc variable.
I do realize there are some redundencies in the code as the more complicated version contains vastly more if/else statements, condition checkers, data processing, etc. You can pare down the code as you see fit.
In my Google Sheet, the first two columns were the timestamp of submission and the name of the person submitting. These were used in the naming of the file.
function mergeApplication() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Merge Data");
var range = sheet.getActiveRange();
var formSheet = ss.getSheetByName("Form Responses");
var lastRow = formSheet.getLastRow();
var lastColumn = sheet.getMaxColumns();
function checkAndComplete() {
var urlColumn = lastColumn;
var checkColumn = (urlColumn - 1);
var checkRange = sheet.getRange(2, checkColumn, (lastRow - 1), 1);
var check = checkRange.getBackgrounds();
var red = "#ff0404";
var yellow = "#ffec0a";
var green = "#3bec3b";
for (var i = 0; i < check.length; i++) {
if (check[i] == green) {
continue;
} else {
var statusCell = sheet.getRange((i+2), checkColumn, 1, 1);
var urlCell = sheet.getRange((i+2), urlColumn, 1, 1);
var dataRow = sheet.getRange((i+2), 1, 1, (lastColumn - 2));
function mergeTasks() {
function docCreator() {
var docTemplate = DriveApp.getFileById("docid");
var docToUse = docTemplate;
var folderDestination = DriveApp.getFolderById("folderid");
var name = sheet.getRange((i+2), 2, 1, 1).getValue();
var rawSubmitDate = sheet.getRange((i+2), 1, 1, 1).getValue();
var submitDate = Utilities.formatDate(rawSubmitDate, "PST", "MM/dd/yy");
var docName = "File Name - " + name + " - " + submitDate;
var docCopy = docToUse.makeCopy(docName, folderDestination);
var docId = docCopy.getId();
var docURL = DriveApp.getFileById(docId).getUrl();
var docToSend = DriveApp.getFileById(docId);
var docInUse = DocumentApp.openById(docId);
var docBody = docInUse.getBody();
var docText = docBody.getText();
function tagReplace() {
var taggedArray = docText.match(/\<{2}[\w\d\S]+\>{2}/g);
var headerArray = sheet.getRange(1, 1, 1, (lastColumn - 2)).getValues();
var dataArray = dataRow.getValues();
var strippedArray = [];
function tagStrip() {
for (var t = 0; t < taggedArray.length; t++) {
strippedArray.push(taggedArray[t].toString().slice(2, -2));
}
}
function dataMatch() {
for (var s = 0; s < strippedArray.length; s++) {
for (var h = 0; h < headerArray[0].length; h++) {
if (strippedArray[s] == headerArray[0][h]) {
docBody.replaceText(taggedArray[s], dataArray[0][h]);
}
}
}
docInUse.saveAndClose();
}
tagStrip();
dataMatch();
}
tagReplace();
statusCell.setBackground(yellow);
var pdfDocBlob = docToSend.getAs(MimeType.PDF);
var pdfDocInitial = DriveApp.createFile(pdfDocBlob).setName(docName);
var pdfDoc = pdfDocInitial.makeCopy(folderDestination);
pdfDocInitial.setTrashed(true);
urlCell.setValue(docURL);
}
statusCell.setBackground(red);
docCreator();
statusCell.setBackground(green);
}
mergeTasks();
}
}
}
checkAndComplete();
}
This process will run through systematically, takes about 5 seconds per row, and will be creating each file at the root of your Drive but quickly deletes it from the root. There may be a more simple way to perform this that saves space in your trash but I did not research more efficient methods.

Related

Is there any way that we can translate a whole Google Docs file using Google (Apps) Script, BUT still keep all the formats, tables, images intact?

I'm trying to imitate the "Translate Document" tool in Google Docs by writing a Google (Apps) Script that can automatically translate a series of documents.
I've tried LanguageApp.Translate() but this syntax only returns an unformatted string and removes all the table borders (purely string).
This is my code:
function TranslateFunction() {
//Get the files in your indicated Folder
var TargetFolderID = '1VUNGtqiNbnHhIFCXmbdSwNZ-vZ5NWVTE'; //Paste the folder ID here to start
var folder = DriveApp.getFolderById(TargetFolderID);
var files = folder.getFiles();
//Get all the files' ID in the folder above
while (files.hasNext()){
var file = files.next();
var fileID = file.getId();
//Convert each file in the folder from Docx (Word) to Docs (Google)
var docx = DriveApp.getFileById(fileID);
var newDoc = Drive.newFile();
var blob = docx.getBlob();
var file=Drive.Files.insert(newDoc,blob,{convert:true});
DocumentApp.openById(file.id).setName(docx.getName().slice(0,-5));
//Activate the file
var doc = DocumentApp.openById(file.id);
//Create a new Docs file to put the translation in + Name
var newDoc = DocumentApp.create("|EN| " + docx.getName().slice(0,-5));
//Get the text in the file
var bodyText = doc.getBody().getText();
//Translate the text and append it into the new Docs
Translatedtext = LanguageApp.translate(bodyText,'vi','en');
newDoc.getBody().appendParagraph(Translatedtext);
}
}
Came across the same challenge and what seems to work for me is first creating a copy of the document I'm trying to translate and then looping through all paragraphs and translating them one by one.
In my use case I wanted to coordinate this from a Google Sheet as I mostly need multiple different translations from the same Doc that then need to be reviewed by colleagues, therefore I'd usually put source Doc URLs into a sheet, run a script that translates it to all needed languages and then feeds back the URLs into the sheet.
For source and target language use ISO-639 codes.
function translateDoc (sourceURL, sourceLang, targetLang) {
// get ID from URL, open file and get ID from the folder it's in
var sourceDocId = sourceURL.match(/[-\w]{25,}/);
var sourceDoc = DriveApp.getFileById(sourceDocId);
var sourceTitle = sourceDoc.getName();
var sourceFolderId = sourceDoc.getParents().next().getId();
// create a copy in the same folder and indicate the language in the title
var targetTitle = "[" + String(targetLang).toUpperCase() + " translation] " + sourceTitle;
var targetDoc = sourceDoc.makeCopy(targetTitle, DriveApp.getFolderById(sourceFolderId));
// open copied Doc and start looping through the content
var doc = DocumentApp.openById(targetDoc.getId());
var body = doc.getBody();
var elements = body.getParagraphs();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var translatedText = LanguageApp.translate(element.getText(), sourceLang, targetLang);
if(translatedText != "") element.setText(translatedText);
// I had an error with an empty paragraph, so avoiding this by only updating when not empty
}
doc.saveAndClose();
return "https://docs.google.com/document/d/" + targetDoc.getId(); // returns URL of translated doc
}
function translateDocs () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rows = sheet.getDataRange().getValues();
var languages = ["de", "fr", "es", "it", "nl", "pl", "pt"];
var sourceFileURL, translationDoc;
rows.forEach(function(row, index){
if (index === 0) return; // table headers
if (row[0] == "") return; // no source file
if (row[1] != "") return; // column 2 not empty --> already translated this file
sourceFileURL = row[0];
for (var i = 0; i < languages.length; i++) {
translationDoc = translateDoc(sourceFileURL, "en", languages[i]);
sheet.getDataRange().getCell(parseInt(index) + 1,parseInt(i) + 2).setValue(translationDoc);
}
});
}

"TypeError: Cannot read property 'getBlob' of undefined" when trying to get two different images and insert them into a document

I have a Google Apps Script that automatically extracts data from a Google Sheet and inserts it into a pre specified template. The data is found using unique tagNumbers/identifiers.
The data being extracted includes 3 signatures. I can only extract one of these signatures before I encounter the aforementioned error: TypeError: Cannot read property 'getBlob' of undefined.
This code is being used in two different functions, using all the same variables and names. I have tried changing variable names but this has resulted in the same TypeError.
The spreadsheet can be found here.
The script is here.
And the document template being filled is here.
Here is the code.
function electInstallSignature(row, body){
var signature = row[17];
var sign = signature.substring(signature.indexOf("/") + 1);
var sigFolder = DriveApp.getFolderById("16C0DR-R5rJ4f5_2T1f-ZZIxoXQPKvh5C");
var files = sigFolder.getFilesByName(sign);
var n = 0;
var file;
while(files.hasNext()){
file = files.next();
n++;
} if(n>1){
SpreadsheetApp.getUi().alert('there is more than one file with this name' + sign);
}
var sigElectInstaller = "%SIGNELECTINSTALL%";
var targetRange = body.findText(sigElectInstaller); // Finding the range we need to focus on
var paragraph = targetRange.getElement().getParent().asParagraph(); // Getting the Paragraph of the target
paragraph.insertInlineImage(1, file.getBlob());// As there are only one element in this case you want to insert at index 1 so it will appear after the text // Notice the .getBlob()
paragraph.replaceText(sigElectInstaller, ""); // Remove the placeholder
}
function commEngineerSignature(row, body){
var signature = row[35];
var sign = signature.substring(signature.indexOf("/") + 1);
var sigFolder = DriveApp.getFolderById("16C0DR-R5rJ4f5_2T1f-ZZIxoXQPKvh5C");
var files = sigFolder.getFilesByName(sign);
var n = 0;
var file;
while(files.hasNext()){
file = files.next();
n++;
} if(n>1){
SpreadsheetApp.getUi().alert('there is more than one file with this name' + sign);
}
var sigCommEngineer = "%SFCE%";
var targetRange = body.findText(sigCommEngineer); // Finding the range we need to focus on
var paragraph = targetRange.getElement().getParent().asParagraph(); // Getting the Paragraph of the target
paragraph.insertInlineImage(1, file.getBlob());// As there are only one element in this case you want to insert at index 1 so it will appear after the text // Notice the .getBlob()
paragraph.replaceText(sigCommEngineer, ""); // Remove the placeholder
}
As you can see, the code is the exact same in both functions, but only works in the electInstallSignature(row, body) function.
Below you can find where the row and body parameters are declared.
function chooseRowMethodI(templateId, rowNumber){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
var data = sheet.getRange(2, 2, 10, 41).getValues();//starting with row 2 and column 1 as our upper-left most column, get values from cells from 1 row down, and 15 columns along - hence (2,1,1,15)
var docTitle = sheet.getRange(2, 2, 10, 1).getValues();//this is grabbing the data in field B2
var docTitleTagNumber = sheet.getRange(2, 5, 11, 1).getValues();
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth() + 1;
var yyyy = today.getFullYear();
today = dd + '/' + mm + '/' + yyyy;
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response){
Logger.log(i);
var row = data[rowNumber];
var docId = DriveApp.getFileById(templateId).makeCopy().getId();
var doc = DocumentApp.openById(docId);
var body = doc.getActiveSection();
//************************** All Instruments data in here**********************
instrumentDetails(body, row);
electInstallSignature(row, body);
commEngineerSignature(row, body);
doc.saveAndClose();
var file = DriveApp.getFileById(doc.getId());
var newFolder = DriveApp.getFolderById("1Jylk3uO_WU0ClLQdm9y-mwRfHxlh2Ovn");
newFolder.addFile(file);
var newDocTitle = docTitle[i - 1][0];
var newDocTagNumber = docTitleTagNumber[i - 1][0];
doc.setName(newDocTitle + " " + newDocTagNumber + " " + today);
}
}
}
}
Should it be required, I have included the function where everything is run from (note that any ui and user input code is tabbed out to avoid having to navigate back to the spreadsheet every time the code is run).
var response = "FT101";
function chooseRow(){
// var ui = SpreadsheetApp.getUi(); // Same variations.
// var result = ui.prompt('Please enter the Tag number of the row you wish to print.', ui.ButtonSet.OK_CANCEL);
//
// // Process the user's response.
// var button = result.getSelectedButton();
// response = result.getResponseText();
// if (button == ui.Button.OK) {
// // User clicked "OK".
// ui.alert('Your tag number is' + response + '.');
// } else if (button == ui.Button.CANCEL) {
// // User clicked X in the title bar.
// ui.alert('You closed the dialog.');
// return 'the end';
// }
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
var category = sheet.getRange(2, 3, 11, 1).getValues();//Needs to be verified to ensure correct cell is chosen by script
var tags = sheet.getRange(2, 5, 11, 1).getValues();//Needs to be verified to ensure correct cell is chosen by script
for(var i = 0; i < tags.length; i++){
if(tags[i][0] == response && category[i][0] == "Instrument"){
var templateId = "1N3o951ECS5CAVGE6UgqBiCPC7H7LiJbL7Cd59G1xTnA";
chooseRowMethodI(templateId, i);
return "";
} else if(tags[i][0] == response && category[i][0] == "Motor" || tags[i][0] == response && category[i][0] == "Valve"){
var templateId = "1cSPD23qFd-34-IIr5eJ5a5OgHp9YR6xav9T28Y4Msec";
chooseRowMethodMV(templateId, i);
return "";
}
}
}
This errors TypeError: Cannot read property 'getBlob' of undefined means that the object you are trying to getBlob from it does not have any blob data.
The only difference with the frist function and the second one is the first line: row[17] instead of row[35] this means the following:
var signature = row[17];
var sign = signature.substring(signature.indexOf("/") + 1);
var sigFolder = DriveApp.getFolderById("16C0DR-R5rJ4f5_2T1f-ZZIxoXQPKvh5C");
var files = sigFolder.getFilesByName(sign);
var n = 0;
var file;
while(files.hasNext()){
file = files.next();
n++;
} if(n>1){
SpreadsheetApp.getUi().alert('there is more than one file with this name' + sign);
}
So, you are probably never accessing to the while loop:
while(files.hasNext())
because var files = sigFolder.getFilesByName(sign); never had a next, and thus, as file is not initialized, it is undefined.
In summary, the error you are getting is:
file is undefined
This means that you never assigned anything on this variable, which only happens if you never accessed the while, which only happens if files never had a next.
Which happens because there aren't any files at all there, this means that there isn't any file with the name sign on the sigFolder. Or that the row[17] does not contain any substantial information about the filename you want to access.
So, check this.
Also, take into account the following documentation about iterators like the one you are handling on files:
When you do files.next() you are accesing the first element of the iterator:
File iterator
General documentation on JavaScript iterators:
Iterators and generators on Javascript

Google Script is outputting image file path instead of image

I am working on an app for use on sites that deal with water treatment and waste management.
This app is being developed using appsheet.com
Appsheet creates apps using spreadsheets and stores all data input into the app in these spreadsheets.
I have a script that extracts data from a spreadsheet and applies it to a Google doc template.
The at the end of the template there is a signature section.
Appsheet has a built in signature feature, which stores these signatures as a png file in a subfolder.
In the spreadsheet, the image is kept as a file path, as seen here
When run, the script interprets this as the text you see and applies it to my template.
I need to be able to read this and apply the image, not the file path.
I have tried using some of the various image classes explained on the Apps Script Reference, none of which have worked for me.
function chooseRowMethod(templateId){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
var data = sheet.getRange(2, 2, 11, 18).getValues();//starting with row 2 and column 1 as our upper-left most column, get values from cells from 1 row down, and 15 columns along - hence (2,1,1,15)
var docTitle = sheet.getRange(2, 2, 11, 1).getValues();//this is grabbing the data in field B2
var docTitleTagNumber = sheet.getRange(2, 3, 11, 1).getValues();
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth() + 1;
var yyyy = today.getFullYear();
today = dd + '/' + mm + '/' + yyyy;
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response){
Logger.log(i);
var row = data[i - 1];
var docId = DriveApp.getFileById(templateId).makeCopy().getId();
var doc = DocumentApp.openById(docId);
var body = doc.getActiveSection();
body.replaceText("%SITEID%", row[0]);
...
body.replaceText("%SIGNED%", row[16]);
doc.saveAndClose();
var file = DriveApp.getFileById(doc.getId());
var newFolder = DriveApp.getFolderById("16wRGBVdV0OZ5YfKhqEQSFMsux-ekGCCa");
newFolder.addFile(file);
var newDocTitle = docTitle[i - 1][0];
var newDocTagNumber = docTitleTagNumber[i - 1][0];
doc.setName(newDocTitle + " " + newDocTagNumber + " " + today);
}
}
}
}
I am aware that the body.replaceText() line does exactly as it says. I have tried using body.setImage(), but this also failed to get any results.
The expect result is picture that is taken from its folder and applied to the template using the file path that is given in the spreadsheet.
Problem
You want to insert image from Google Drive to Google Docs document body.
Solution
Use the InlineImage element (class) and corresponding appendImage() or insertImage() methods. This is done in three simple steps:
Access your image file (e.g. DriveApp.getFileById() or similar, it's up to you);
Get file data as Blob class instance via getBlob() method;
invoke appendImage() or insertImage() with blob as argument (note that insertImage() requires child element index as its first argument).
Sample
function insertImage() {
var doc = DocumentApp.create('TEST');
var body = doc.getBody();
var image = DriveApp.getFileById('yourIdHere').getBlob();
var inline = body.appendImage(image);
}
Reference
insertImage() method reference;
appendImage() method reference;
getBlob() method reference;
After spending some time on other parts of this project, I have come back to this problem and found a solution using the following code.
var signature = row[17];
var sign = signature.substring(signature.indexOf("/") + 1);
var sigFolder = DriveApp.getFolderById("16C0DR-R5rJ4f5_2T1f-ZZIxoXQPKvh5C");
var files = sigFolder.getFilesByName(sign);
var n = 0;
var file;
while(files.hasNext()){
file = files.next();
n++;
} if(n>1){
SpreadsheetApp.getUi().alers('there is more than one file with this name' + sign);
}
var sigElectInstaller = "%SIGNELECTINSTALL%";
var targetRange = body.findText(sigElectInstaller); // Finding the range we need to focus on
var paragraph = targetRange.getElement().getParent().asParagraph(); // Getting the Paragraph of the target
paragraph.insertInlineImage(1, file.getBlob());// As there are only one element in this case you want to insert at index 1 so it will appear after the text // Notice the .getBlob()
paragraph.replaceText(sigElectInstaller, ""); // Remove the placeholder

getFilesByType not working

I am trying to update my code to work with the new DriveApp API update. I got most of it working (I think) but it is hanging up on line 68 saying that it can't getFilesByType. I did try and change the loop for adding to be based off the iterator (I think) but still get this error: TypeError: Cannot find function getFilesByType in object FolderIterator
Any help would be appreciated thanks!
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
Template Generator By: Andre Fecteau - klutch2013#gmail.com
Original Code From: kiszal#gmail.com (Found in the template gallery by searching "Templates" It is the first One.
Major Help from: Serge Insas On Stack Overflow (He did most of the work.)
Link 1: http://stackoverflow.com/questions/18147798/e-undefined-google-script-error
Link 2: http://stackoverflow.com/questions/18132837/have-a-listbox-populate-with-every-folder-in-mydrive
How To Use:
First: each column is designated in your Template by {Column Letter} for example for column A it would be {A}
Second: You can change this, but your Template must be in a folder called "Templates." This folder can be anywhere in your drive.
Third: Click "Generate Documents Here!" Then click "Export Row to Document"
Fourth: Type in the row you want to export. Chose your Folder Path. Click Submit.
NOTE ON FOURTH STEP: If you want your number to skip the header row add a +1 to line 32.
This would mean if you typed "2" in the row box it actually exports row 3. I took this out because it can get confusing at times.
NOTE: Line 71 you can edit the word "Templates" to whatever folder you saved your Template into.
NOTE: Line 36 you can edit to change what comes up in the name of the file that is created. To do this just edit the column letter in the following piece: "+Sheet.getRange('E'+row).getValue()+"
You can then delete any other columns you do not want by deleteing the section of code that looks like the following: '+Sheet.getRange('D'+row).getValue()+'
Feel free to edit this code as you wish and for your needs. That is what I did with the original code.
So there is no reason I should restrict what others do with this code.
Bug Fix Log:
* changed row: 32 to remove the +1 6/18/2014
* changed row: 40 to remov e the row-- 6/18/2014
* changed row: 68 to update to DriveApp because of Google API Update - 4/21/15
* changed row: 68 to update to getFoldersByName and getFilesByType to update to new Google API - 4/21/15
*/
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function generateDocument(e) {
var template = DriveApp.getFileById(e.parameter.Templates);
Logger.log(template.getName());
var Sheet = SpreadsheetApp.getActiveSpreadsheet();
var row = Number(e.parameter.row) //+1; // Remove the // in this line next to the +1 to skip headers
Logger.log(row);
var currentFID = e.parameter.curFID;
Logger.log(currentFID);
var myDocID = template.makeCopy(Sheet.getRange('B' + row).getValue() + ' - ' + Sheet.getRange('E' + row).getValue() + ' - ' + Sheet.getRange('D' + row).getValue() + ' - ' + Sheet.getRange('X' + row).getValue()).getId();
var myDoc = DocumentApp.openById(myDocID);
var copyBody = myDoc.getActiveSection();
var Sheet = SpreadsheetApp.getActiveSpreadsheet();
//row--; // decrement row number to be in concordance with real row numbers in sheet
var myRow = SpreadsheetApp.getActiveSpreadsheet().getRange(row + ":" + row);
for (var i = 1; i < Sheet.getLastColumn() + 1; i++) {
var myCell = myRow.getCell(1, i);
copyBody.replaceText("{" + myCell.getA1Notation().replace(row, "") + "}", myCell.getValue());
}
myDoc.saveAndClose();
var destFolder = DriveApp.getFolderById(currentFID);
Logger.log(myDocID);
var doc = DriveApp.getFileById(myDocID); // get the document again but using DriveApp this time...
doc.addToFolder(destFolder); // add it to the desired folder
doc.removeFromFolder(DriveApp.getRootFolder()); // I did it step by step to be more easy to follow
var pdf = DriveApp.getFileById(myDocID).getAs("application/pdf");
destFolder.createFile(pdf); // this will create the pdf file in your folder
var app = UiApp.getActiveApplication();
app.close();
return app;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function getTemplates() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var app = UiApp.createApplication().setTitle('Generate from template');
// Create a grid with 3 text boxes and corresponding labels
var grid = app.createGrid(5, 2);
grid.setWidget(0, 0, app.createLabel('Template name:'));
var list = app.createListBox();
list.setName('Templates');
grid.setWidget(0, 1, list);
var docs = DriveApp.getFoldersByName("Templates").getFilesByType(documents); //Change the word "Templates" to whatever folder you saved your template into
/* OLD LOOP
for (var i = 0; i < docs.length; i++) {
list.addItem(docs[i].getName(),docs[i].getId());
}
*/
while (docs.hasNext()) {
var docs = docs.next();
list.addItem(docs[i].getName(), docs[i].getId());
}
grid.setWidget(1, 0, app.createLabel('Row:'));
var row = app.createTextBox().setName('row');
row.setValue(SpreadsheetApp.getActiveSpreadsheet().getActiveRange().getRow());
grid.setWidget(1, 1, row);
var curFN = app.createTextBox().setText('MyDrive/').setName('curFN').setId('curFN').setWidth('400');
var curFID = app.createTextBox().setText(DriveApp.getRootFolder().getId()).setName('curFID').setId('curFID').setVisible(false);
var listF = app.createListBox().setName('listF').setId('listF').addItem('Please Select Folder', 'x');
grid.setText(2, 0, 'Type Path:').setWidget(2, 1, curFN).setText(3, 0, 'OR').setText(4, 0, 'Choose Path:').setWidget(4, 1, listF).setWidget(3, 1, curFID);
var folders = DriveApp.getRootFolder().getFolders();
for (var i = 0; i < folders.length; i++) {
listF.addItem(folders[i].getName(), folders[i].getId())
}
var handlerF = app.createServerHandler('folderSelect').addCallbackElement(grid);
listF.addChangeHandler(handlerF);
var panel = app.createVerticalPanel();
panel.add(grid);
var button = app.createButton('Submit');
var handler = app.createServerClickHandler('generateDocument');
handler.addCallbackElement(grid);
button.addClickHandler(handler);
// Add the button to the panel and the panel to the application, then display the application app in the Spreadsheet doc
panel.add(button);
app.add(panel);
doc.show(app);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function folderSelect(e) {
var app = UiApp.getActiveApplication();
var currentFN = e.parameter.curFN;
var currentFID = e.parameter.listF;
Logger.log(currentFID);
var listF = app.getElementById('listF');
var curFN = app.getElementById('curFN');
var curFID = app.getElementById('curFID');
if (currentFID == 'x') {
currentFID = DriveApp.getRootFolder().getId();
curFN.setText('MyDrive/')
};
var startFolder = DriveApp.getFolderById(currentFID);
var folders = startFolder.getFolders();
listF.clear().addItem('No More Sub Folders!', 'x').addItem('Go back to Root', 'x');
if (folders.length > 0) {
listF.clear();
listF.addItem('Select Sub Folder', 'x')
};
for (var i = 0; i < folders.length; i++) {
listF.addItem(folders[i].getName(), folders[i].getId())
}
curFN.setText(currentFN + DriveApp.getFolderById(currentFID).getName() + '/');
if (currentFID == DriveApp.getRootFolder().getId()) {
curFN.setText('MyDrive/')
};
curFID.setText(currentFID);
return app;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [{
name: "Export Row to Document",
functionName: "getTemplates"
}];
ss.addMenu("Generate Documents Here!", menuEntries);
}
You need two while loops:
var folders = DriveApp.getFoldersByName("Templates");
while (folders.hasNext()) {
var folder = folders.next();
Logger.log(folder.getName());
var allMyFilesByType = folder.getFilesByType(mimeType)
};
while (allMyFilesByType.hasNext()) {
var file = allMyFilesByType.next();
Logger.log(file.getName());
};
For MIME Types, see:
Google Documentation - MIME Types

Google Script to copy rows to another spread and allow for editing of copy data

I have a form that populates a master spreadsheet, and I have another spreadsheet that gets data from the master sheet. Using the script below I can't edit the information that is copied because whenever the script runs it replaces the edited information to match the master sheet. How can I get the script to only copy data if data hasn't been entered.
ColumnQ's data never changes.
function myFunction() {
var source = SpreadsheetApp.openById('XXX');
var sourcesheet = source.getSheetByName('Lead Sheet');
var target = SpreadsheetApp.openById('XXX')
var targetsheet = target.getSheetByName('Lead Sheet');
var targetrange = targetsheet.getRange(2, 1, sourcesheet.getLastRow(), sourcesheet.getLastColumn());
var rangeValues = sourcesheet.getRange(2, 1, sourcesheet.getLastRow(), sourcesheet.getLastColumn()).getValues();
targetrange.setValues(rangeValues);
}
Do you mean you only want to copy over new rows from the "master" sheet that aren't already in the "target" sheet? If so, assuming column Q is like your "unique key," then you'll have to pull at least this column from each sheet and compare the results. Here's some tested code that does what I believe you want:
function myFunction() {
var qOffset = 16;
var ss = SpreadsheetApp.openById('');
var sourceSheet = ss.getSheetByName('Source');
var targetSheet = ss.getSheetByName('Target');
var sourceValues = sourceSheet.getDataRange().getValues();
var targetValues = targetSheet.getDataRange().getValues();
var deltaValues = [];
for(var i = 0 ; i < sourceValues.length ; i++){
var rowsMatch = false;
for(var j = 0 ; j < targetValues.length ; j++){
if(sourceValues[i][qOffset] == targetValues[j][qOffset]){
rowsMatch = true;
break;
}
}
if(!rowsMatch){
deltaValues.push(sourceValues[i]);
}
}
if(deltaValues.length > 0){
targetSheet.getRange(targetSheet.getLastRow() + 1, 1, deltaValues.length, deltaValues[0].length).setValues(deltaValues);
}
}
Also, I just recently tackled a similar project, but instead of using a "master" sheet, I wrote a function that's triggered by form submissions. When the function is called, it searches my sole spreadsheet for the data; if it finds it, it updates the row, if not, it appends a new row. If that's of any interest to you, I can post parts of the source code.