I am trying to copy a table from Google Sheet that includes pictures into a Google doc.
So far I managed to accomplish several things, but not the result that I need.
Source Google Sheet - table with pictures, filled by Importrange formula from another Google sheet.
Copying via appendTable does not copy pictures
I managed to export table to a pdf blob, but couldn't convert it to a regular Google doc then.
Any help appreciated.
function printDocument() {
var source = SpreadsheetApp.getActive();
var spreadsheetId=source.getId();
var sourcesheet = source.getSheetByName('Printable Shooting Plan');
var sheetId = sourcesheet.getSheetId();
var current_lastrow = getLastPopulatedRow(sourcesheet);
var srcData = sourcesheet.getRange('A1:G'+current_lastrow).getValues();
var parents = DriveApp.getFileById(source.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
else {
folder = DriveApp.getRootFolder();
}
var doc = DocumentApp.create("Shooting Plan");
var body = doc.getBody();
body.insertParagraph(0, 'Shooting Plan').setHeading(DocumentApp.ParagraphHeading.HEADING1).setAlignment(DocumentApp.HorizontalAlignment.CENTER);
table = body.appendTable(srcData);
table.getRow(0).editAsText().setBold(true);
// Copy doc to the directory we want it to be in. Delete it from root.
var docFile = DriveApp.getFileById(doc.getId());
folder.addFile(docFile);
DriveApp.getRootFolder().removeFile(docFile);
}
function onOpen() {
var submenu = [{name:"Save Doc", functionName:"printDocument"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu('Export', submenu);
}
function getLastPopulatedRow(sheet) {
var data = sheet.getDataRange().getValues();
for (var i = data.length-1; i > 0; i--) {
for (var j = 0; j < data[0].length; j++) {
if (data[i][j]) return i+1;
}
}
return 0;
}
Related
Currently I have been using this script which imports data from a spreadsheet located in my google drive. The function works but imports the data one line at a time. Some times these sheets are 400+ rows and that takes a long time. I am looking for it to grab all data and import it into an existing spreadsheet and the end of the last value.
function getData() {
get_files = ['July1-2022'];
var ssa = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ssa.getSheetByName('CancelRawData');
for(z = 0; z < get_files.length; z++)
{
var files = DriveApp.getFilesByName(get_files[z]);
while (files.hasNext())
{
var file = files.next();
break;
}
var ss = SpreadsheetApp.open(file);
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for(var i = 0; i < sheets.length; i++)
{
var nameSheet = ss.getSheetByName(sheets[i].getName());
var nameRange = nameSheet.getDataRange();
var nameValues = nameRange.getValues();
for(var y = 1; y < nameValues.length; y++)
{
copySheet.appendRow(nameValues[y]);
}
}
}
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been all imported", SpreadsheetApp.getUi().ButtonSet.OK);
}
I believe your goal is as follows.
You want to reduce the process cost of your script.
Modification points:
appendRow is used in a loop. The process cost will be high.
In your script, sheets can be written by var sheets = SpreadsheetApp.open(file).getSheets();.
When these points are reflected in your script, it becomes as follows.
Modified script 1:
This script uses the Spreadsheet service (SpreadsheetApp).
function getData() {
// Retrieve values.
get_files = ['July1-2022'];
var values = [];
for (z = 0; z < get_files.length; z++) {
var files = DriveApp.getFilesByName(get_files[z]);
while (files.hasNext()) {
var file = files.next();
break;
}
var sheets = SpreadsheetApp.open(file).getSheets();
for (var i = 0; i < sheets.length; i++) {
values = [...values, ...sheets[i].getDataRange().getValues()];
}
}
if (values.length == 0) return;
// Put values.
const maxLen = Math.max(...values.map(r => r.length));
values = values.map(r => [...r, ...Array(maxLen - r.length).fill(null)]);
var copySheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('CancelRawData');
copySheet.getRange(copySheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been all imported", SpreadsheetApp.getUi().ButtonSet.OK);
}
Modified script 2:
This script uses Sheets API. Before you use this script, please enable Sheets API at Advanced Google services.
function getData() {
// Retrieve values.
get_files = ['July1-2022'];
var values = [];
for (z = 0; z < get_files.length; z++) {
var files = DriveApp.getFilesByName(get_files[z]);
while (files.hasNext()) {
var file = files.next();
break;
}
var spreadsheetId = file.getId();
var ranges = SpreadsheetApp.open(file).getSheets().map(s => `'${s.getSheetName()}'!${s.getDataRange().getA1Notation()}`);
values = [...values, ...Sheets.Spreadsheets.Values.batchGet(spreadsheetId, { ranges }).valueRanges.flatMap(({ values }) => values)];
}
if (values.length == 0) return;
// Put values.
var dstSS = SpreadsheetApp.getActiveSpreadsheet();
Sheets.Spreadsheets.Values.append({ values }, dstSS.getId(), 'CancelRawData', { valueInputOption: "USER_ENTERED" });
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been all imported", SpreadsheetApp.getUi().ButtonSet.OK);
}
Note:
When these scripts are run, all files of get_files are retrieved and all values are retrieved from all sheets in all Spreadsheets, and the retrieved values are put to the destination sheet of CancelRawData.
References:
getValues()
setValues(values)
Method: spreadsheets.values.batchGet
Method: spreadsheets.values.append
I'm trying to create an export to csv apps script but it will not delete existing csv files sitting in the same folder. I would like to either trash them or overwrite them.
Here's the code I've already tried
function saveAsCSV() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
// create a folder from the name of the spreadsheet
// var folder = DriveApp.createFolder(ss.getName().toLowerCase().replace(/ /g,'_') + '_csv_' + new Date().getTime());
// var folder = DriveApp.createFolder(ss.getName().toLowerCase().replace(/ /g,'_') + '_csv_');
for (var i = 0 ; i < sheets.length ; i++) {
var sheet = sheets[i];
// append ".csv" extension to the sheet name
fileName = sheet.getName() + ".csv";
// convert all available sheet data to csv format
var csvFile = convertRangeToCsvFile_(fileName, sheet);
var file = DriveApp.getFileById(ss.getId());
var folder = file.getParents();
folder = folder.next();
// loop through files and delete ones with existing name
var existingfiles = folder.getFiles()
for (var j = 0 ; j<existingfiles.length;j++){
var existingfile = existingfiles[j].next()
if (existingfile.getName()!=ss.getName()){
//to delete
//existingfile.setTrashed(true);
folder.removeFile(existingfile);
}
}
//create new file
folder.createFile(fileName, csvFile);
}
}
I'd expect all files that don't share the same name as the spreadsheet in that folder to get removed, then a csv for each tab to get created. Instead, I get duplicates of each csv file.
Thanks a lot for you help. The while loop helped a bunch. Here's what worked for me in the end.
function saveAsCSV() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var file = DriveApp.getFileById(ss.getId());
var folder = file.getParents();
folder = folder.next();
// loop through files and delete ones with existing name
var xfiles=folder.getFiles();
while(xfiles.hasNext()) {
var fi=xfiles.next();
if(fi.getMimeType()!=MimeType.GOOGLE_SHEETS){fi.setTrashed(true);}
}
// create new csv files
for (var i = 0 ; i < sheets.length ; i++) {
var sheet = sheets[i];
// append ".csv" extension to the sheet name
fileName = sheet.getName() + ".csv";
// convert all available sheet data to csv format
var csvFile = convertRangeToCsvFile_(fileName, sheet);
//create new file
folder.createFile(fileName, csvFile);
}
};
Try this:
function trashOthers() {
var ss=SpreadsheetApp.getActive();
var shts=ss.getSheets();
var fldr=DriveApp.getFileById(ss.getId()).getParents();
var n=0;
while(fldr.hasNext()) {
var folder=fldr.next();
n++;
}
if(n>1){throw('More than one Folder');}
for (var i=0;i<shts.length;i++) {
var sh=shts[i];
var fileName=sh.getName() + ".csv";
var csvFile=convertRangeToCsvFile_(fileName, sh);
var xfiles=folder.getFiles();
while(xfiles.hasNext()) {
xfiles.next().setTrashed(true);
}
folder.createFile(fileName, csvFile);
}
}
Perhaps this is better:
function deleteOthers() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var shts=ss.getSheets();
var fldr=DriveApp.getFileById(ss.getId()).getParents();
var n=0;
while(fldr.hasNext()) {
var folder=fldr.next();
n++;
}
if(n>1){throw('More than one Folder');}
for (var i=0;i<shts.length;i++) {
var sh=shts[i];
var fileName=sh.getName() + ".csv";
var csvFile=convertRangeToCsvFile_(fileName, sh);
var xfiles=folder.getFiles();
while(xfiles.hasNext()) {
var fi=xfiles.next();
if(fi.getMimeType()!=MimeType.GOOGLE_SHEETS){fi.setTrashed(true);}
}
folder.createFile(fileName, csvFile);
}
}
I used deleteOthers() and trashothers() both are deleting the google form which writes data into csv
I would like to be able to export a single specific sheet from a large workbook without having to hide the unrequired sheets. Is that actually possible with Google Scripts?
At the moment I am looping through a list of products, updating a query for each one and then exporting each result to an individual PDF. Basically creating a product "Printout" page for many products.
The code below works quite nicely but it starts by hiding all sheets other than my Printout page. That would be fine except some of the other sheets are protected and not all users that would be using my export functionality have the right to hide sheets.
I've considered adding an unprotect/protect function to my macro but it would be good to know if exporting a single sheet was an option before i went down this route?
The hiding sheets trick was from this post Export Single Sheet to PDF in Apps Script
function exportLoopedSheet(firstRow, lastRow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetName = 'Printout'; // update for print sheet name
var productSheetName = 'ProductList'; // update for final product list
var folderName = 'productPDFs';
var main = ss.getSheetByName(sheetName);
var sheets = ss.getSheets();
var productList = ss.getSheetByName(productSheetName);
var lastProductRow = lastRow;
var firstProductRow = firstRow;
// Hide all sheets other than the Print Sheet
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].getSheetName() !== sheetName) {
sheets[i].hideSheet();
}
}
for (var prodNo = firstProductRow; prodNo < lastProductRow + 1; prodNo ++) {
var currentProduct = productList.getRange('A'+ prodNo).getValue();
main.getRange('B9').setValue(currentProduct);
// Ensure all changes are updated
SpreadsheetApp.flush();
// call the export sheet function
exportSheet();
}
// Unhide the sheets
for (i = 0; i < sheets.length; i++) {
sheets[i].showSheet();
}
}
function exportSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetName = 'Printout';
var main = ss.getSheetByName(sheetName);
var sheets = ss.getSheets();
//Hide All Empty Rows in the Print Sheet
var maxRows = main.getMaxRows();
var lastRow = main.getLastRow();
if (maxRows-lastRow != 0){
main.hideRows(lastRow+1, maxRows-lastRow);
}
// Save pdf version
var folder = 'productPDF';
var parentFolder = DriveApp.getFolderById('1234'); //add this line...
var folder, folders = DriveApp.getFoldersByName(folder);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = parentFolder.createFolder(folder);
}
var name = main.getRange("B8").getValue();
folder.createFile(ss.getBlob().setName(name));
// Unhide the rows again
var fullSheetRange = main.getRange(1,1,main.getMaxRows(), main.getMaxColumns());
main.unhideRow(fullSheetRange);
}
I want to accomplish the following:
Make a menu with a print to PDF button.
Have that button export a PDF to with the same name and same destination as the google sheet.
This works well, but the script I found I need to change, because most people print to PDF by making a temporary copy of the sheet, printing, and finally deleting the temporary copy.
I have references from other sheets in my original document that ends up printed as #REF! values because only the one sheet gets copied and printed, not my whole document.
How can i make this process include baking the formulas as text?
CODE FOR PRINTING:
function onOpen() {
var submenu = [{name: "Save PDF", functionName: "generatePdf"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu('Export', submenu);
}
function generatePdf() {
// Get active spreadsheet.
var sourceSpreadsheet = SpreadsheetApp.getActive();
// Get active sheet.
var sheets = sourceSpreadsheet.getSheets();
var sheetName = sourceSpreadsheet.getActiveSheet().getName();
var sourceSheet = sourceSpreadsheet.getSheetByName(sheetName);
// Set the output filename as sheetName.
var pdfName = sheetName;
// Get folder containing spreadsheet to save pdf in.
var parents = DriveApp.getFileById(sourceSpreadsheet.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
else {
folder = DriveApp.getRootFolder();
}
// Copy whole spreadsheet.
var destSpreadsheet = SpreadsheetApp.open(DriveApp.getFileById(sourceSpreadsheet.getId()).makeCopy("tmp_convert_to_pdf", folder))
// Delete redundant sheets.
var sheets = destSpreadsheet.getSheets();
for (i = 0; i < sheets.length; i++) {
if (sheets[i].getSheetName() != sheetName){
destSpreadsheet.deleteSheet(sheets[i]);
}
}
var destSheet = destSpreadsheet.getSheets()[0];
// Replace cell values with text (to avoid broken references).
var sourceRange = sourceSheet.getRange(1, 1, sourceSheet.getMaxRows(), sourceSheet.getMaxColumns());
var sourcevalues = sourceRange.getValues();
var destRange = destSheet.getRange(1, 1, destSheet.getMaxRows(), destSheet.getMaxColumns());
destRange.setValues(sourcevalues);
// Save to pdf.
var theBlob = destSpreadsheet.getBlob().getAs('application/pdf').setName(pdfName);
var newFile = folder.createFile(theBlob);
// Delete the temporary sheet.
DriveApp.getFileById(destSpreadsheet.getId()).setTrashed(true);
}
CODE FOR CONVERTING FORMULAS TO TEXT:
function formulasAsText() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var k = 0; k < sheets.length; k++) {
var range = sheets[k].getDataRange();
var values = range.getValues();
var formulas = range.getFormulas();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[0].length; j++) {
values[i][j] = formulas[i][j] ? "'" + formulas[i][j] : values[i][j];
}
}
range.setValues(values);
}
}
To maintain formula references, you need to modify the formulasAsText() function to accept an input, and also not worry about writing a formula if one is found. This input could be a spreadsheet ID - i.e. the id of the temporary copy - or it could be an array of Sheet objects.
Once you have made these two modifications, you would call the function prior to deleting the non-desired sheets in the temporary copy:
/**
* #param Sheet[] wbSheets An array of Sheets to operate on.
* #param String toPreserve The name of the sheet which should be preserved.
*/
function preserveFormulas(wbSheets, toPreserve) {
if(!wbSheets || !wbSheets.length || !toPreserve)
throw new Error("Missing arguments.");
wbSheets.forEach(function (sheet) {
if ( sheet.getName() === toPreserve ) {
var range = sheet.getDataRange();
// Serialize the cell's current value, be it static or derived from a formula.
range.setValues(range.getValues());
}
});
}
Finished my script. Here is what i ended up with.
function onOpen() {
var submenu = [{name:"Save PDF", functionName:"generatePdf"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu('Export', submenu);
}
function hideSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("sheet1");
var sheet2 = ss.getSheetByName("PRINT SHEET");
var sheet3 = ss.getSheetByName("sheet3");
var sheet4 = ss.getSheetByName("sheet4");
var sheet5 = ss.getSheetByName("sheet5");
var sheet6 = ss.getSheetByName("sheet6");
var sheet7 = ss.getSheetByName("sheet7");
sheet1.hideSheet();
sheet3.hideSheet();
sheet4.hideSheet();
sheet5.hideSheet();
sheet6.hideSheet();
sheet7.hideSheet();
}
function showSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("sheet1");
var sheet2 = ss.getSheetByName("PRINT SHEET");
var sheet3 = ss.getSheetByName("sheet3");
var sheet4 = ss.getSheetByName("sheet4");
var sheet5 = ss.getSheetByName("sheet5");
var sheet6 = ss.getSheetByName("sheet6");
var sheet7 = ss.getSheetByName("sheet7");
sheet1.showSheet();
sheet3.showSheet();
sheet4.showSheet();
sheet5.showSheet();
sheet6.showSheet();
sheet7.showSheet();
}
function generatePdf() {
// Get active spreadsheet.
var sourceSpreadsheet = SpreadsheetApp.getActive();
// Get active sheet.
var sheets = sourceSpreadsheet.getSheets();
var sheetName = sourceSpreadsheet.getActiveSheet().getName();
var sourceSheet = sourceSpreadsheet.getSheetByName(sheetName);
// Set the output filename as BID and number (from cell C4).
var pdfName = "BID " + sheets[0].getRange("C4").getValue() + ".pdf";
var ui = SpreadsheetApp.getUi();
// Get folder containing spreadsheet to save pdf in.
var parents = DriveApp.getFileById(sourceSpreadsheet.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
else {
folder = DriveApp.getRootFolder();
}
hideSheets();
// Save to pdf.
var theBlob = sourceSpreadsheet.getBlob().getAs('application/pdf').setName(pdfName);
var newFile = folder.createFile(theBlob);
showSheets();
ui.alert('Export finished');
}
Thanks.
I'm brand new to Google Apps Script, and I'm trying to create a simple spreadsheet that will allow me to share files by user email through a single spreadsheet.
I have written the following script, which will allow me to add editors and viewers, but not commenters.
I keep getting an error that states that the function addCommenter cannot be found in object spreadsheet.
function shareSheet () {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.toast('Updating access level...');
var sheet = ss.getSheets()[0];
var lastRow = sheet.getLastRow();
var range1 = sheet.getRange (3,1, lastRow,9);
var data = range1.getValues();
for (var i= 0; i < data.length; i++) {
var row = data [i];
var accessLevel = row [5];
var values = row [8];
var ss2 = SpreadsheetApp.openById(values);
var values2 = row [4];
// Add new editor
if (accessLevel == 'Edit') {
var addEditors = ss2.addEditor(values2);
}
// Add new viewer
if (accessLevel == 'View'){
var addViewers = ss2.addViewer(values2);
}
// Add new commenter
if (accessLevel == 'Comment') {
var addCommenters = ss2.addCommenter(values2);
}
}
}
The Spreadsheet object does not support the addCommentor() method. You should use DriveApp service instead.
DriveApp.getFileById(id).addCommenter(emailAddress)