The function only work once. It does not loop at all.
Tweaking by changing the for looping parameter to just number and changing the position of the lines.
function PDFAbsensi() {
var sourceSpreadsheet = SpreadsheetApp.getActive();
// Get active sheet.
var sheets = sourceSpreadsheet.getSheets();
var sheetName = sourceSpreadsheet.getActiveSheet().getName();
var sourceSheet = sourceSpreadsheet.getSheetByName('PRINT ABSENSI');
// Set the output filename.
var idkelompok = sourceSheet.getRange(2,36).getValues();
var namakelompok = sourceSheet.getRange(2,2).getValues();
var nomorkelompok = sourceSheet.getRange(2,3).getValues();
var pdfName = idkelompok + " - " + namakelompok + " " + nomorkelompok;
// export url
var url = 'https://docs.google.com/spreadsheets/d/'+sourceSpreadsheet.getId()+'/export?exportFormat=pdf&format=pdf' // export as pdf / csv / xls / xlsx
+ '&gid='+sourceSpreadsheet.getSheetByName('PRINT ABSENSI').getSheetId();
var token = ScriptApp.getOAuthToken();
var response = UrlFetchApp.fetch(url, {
headers: {
'Authorization': 'Bearer ' + token} });
var parents = DriveApp.getFileById(sourceSpreadsheet.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();}
else {
folder = DriveApp.getRootFolder();}
// Loop
var jumlahpdf = sourceSheet.getRange(5,36).getValue();
for (i=0;i<jumlahpdf;i++) {
var theBlob = response.getBlob().setName(pdfName+'.pdf');
// delete pdf if already exists
var files = folder.getFilesByName(pdfName+'.pdf');
while (files.hasNext())
{files.next().setTrashed(true);}
// create pdf
var newFile = folder.createFile(theBlob);
return true;
// Delete the wasted sheet we created, so our Drive stays tidy.
DocsList.getFileById(newSpreadsheet.getId()).setTrashed(true);
// Add the number for looping function
var updatenomorkelompok = sourceSheet.getRange(2,36).setValue(idkelompok-(-1));}}
I expect the code to create multiple PDF files. As of now it only made one each time it runs.
Issue:
You have return true in the middle of your for loop.
Explanation:
The return is ending the function completely on the first loop. This is expected behaviour, see below from MDN return documentation:
The return statement ends function execution and specifies a value to be returned to the function caller.
Example:
Below is a basic for loop that logs the value of i with every run:
for (i = 0; i < 3; i++) {
console.log(i);
}
This logs every iteration as expected.
Here's an example with the return statement you're using (same for loop, but with return true inside):
testReturn();
function testReturn() {
for (i = 0; i < 3; i++) {
console.log(i);
return true;
}
}
When this function is called, we get a log entry for 0 (the first run), but as we're returning, the function execution is ended, hence why your script is only processing the first PDF.
return
Related
I'm fairly new to the world of aumating sheets with scripts and ma still firmly in the 'cobble other peoples code together' phase of writing scripts.
I have a schedule in Google Sheets that display's different department overviews based on the contents of a cell ('B1'). I'm attempting to iterate a list of values through that cell and in each instance export the resulting sheet to pdf.
So far I've got it working, my next hurdle is getting it to export the pdf in landscape rather than portrait. I can see implementations using url export but I'm not confident enough (read keep breaking everything) to implement that in to the script below.
Any help greatly appreciated!
function PrintPDF(){
var df = DriveApp.getFolderById("folderID");
var arr = new Array();
var files = df.getFiles();
while( files.hasNext() ) {
var f = files.next();
arr.push( [ [ f.getId() ] , [ f.getLastUpdated() ] ] );
}
arr.sort( sortFunction );
function sortFunction( a , b ) {
var aDate = new Date(a[1]);
var bDate = new Date(b[1]);
if ( aDate === bDate ) return 0;
else if ( aDate < bDate ) return 1;
else return -1;
};
for( var i=0 ; i<arr.length ; i++ )
DriveApp.getFileById( arr[i][0] ).setTrashed( true );
//Create array of show codes
var Dss = SpreadsheetApp.getActive().getSheetByName('Value Lists')
var Tss = SpreadsheetApp.getActive().getSheetByName('PrintSheet')
var Shows = Dss.getRange('K3:K11').getValues()
var Count = Shows.length
var Code = Tss.getRange('B1')
Logger.log(Count)
Logger.log(Shows)
for (i=0;i<Count;i++){
Code.setValue(Shows[i])
HideBlankRows ()
const folderName = `foldername`;
var ss =SpreadsheetApp.getActiveSpreadsheet()
var name = "Department - "+ss.getRange('B1').getValue()
Logger.log(name)
DriveApp.getFoldersByName(folderName)
.next()
.createFile(ss
.getBlob()
.getAs(`application/pdf`)
.setName(name)
);
}
Code.setValue('')
}
function HideBlankRows() {
var ss = SpreadsheetApp.getActive();
var sheets = ss.getSheets(); // array of all sheet objects in ss
var numSheets = ss.getNumSheets(); // count of sheets
for(sheet in sheets) {
if(sheets[sheet].getSheetName() == "AA") continue;
//show all the rows
sheets[sheet].showRows(1, sheets[sheet].getMaxRows());
//get data from column A
var data = sheets[sheet].getRange('A:A').getValues();
Logger.log(data)
//iterate over all rows
for(var i=0; i< data.length; i++){
//compare column, if no, then hide row
if(data[i][0] == 'Yes'){
sheets[sheet].hideRows(i+1);
}
}
}
}
Your code looks a bit cryptic to me. I don't understand why do you need the arr array if you nowhere use it. Etc.
But whatever. Suppose the code works fine and all you need is to save a PDF file with landscape orientation. In this case you need to replace these lines in your code:
DriveApp.getFoldersByName(folderName)
.next()
.createFile(ss
.getBlob()
.getAs(`application/pdf`)
.setName(name)
);
With this:
savePDFs(name);
And add at the end of your script the function savePDFs():
function savePDFs(name) {
SpreadsheetApp.flush();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var url = ss.getUrl();
//remove the trailing 'edit' from the url
url = url.replace(/edit$/, '');
//additional parameters for exporting the sheet as a pdf
var url_ext = 'export?exportFormat=pdf&format=pdf' + //export as pdf
//below parameters are optional...
'&size=a4' + //paper size
'&portrait=false' + //orientation, false for landscape, true for portrait
'&fitw=true' + //fit to width, false for actual size
'&sheetnames=false&printtitle=false&pagenumbers=false' + //hide optional headers and footers
'&gridlines=false' + //hide gridlines
'&fzr=false' + //do not repeat row headers (frozen rows) on each page
'&gid=' + sheet.getSheetId(); //the sheet's Id
var token = ScriptApp.getOAuthToken();
var response = UrlFetchApp.fetch(url + url_ext, {
headers: { 'Authorization': 'Bearer ' + token }
});
var blob = response.getBlob().setName(name + '.pdf');
DriveApp.createFile(blob);
}
This function was taken from here: Change document orientation when exporting to PDF
I have the script below which is importing some XML data in a google sheet called prices. Everything works fine except that I have set up a time driven trigger to run every minute but the data won't get updated.
The trigger seems to work fine, as I can see the last run time being updated every minute.
The script calling the XML data works fine as I can see the data being populated in the spreadsheet.
The XML feed works fine too, as I can see the time being updated every minute, also have a cron job.
I only have this function as a project.
function getData() {
var sheetName = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("prices");
var queryString = Math.random();
var cellFunction = '=ImportXML("http://myxmldata.com/data-xml.php","//data/date")';
sheetName.getRange('A2').setValue(cellFunction);
}
So what's wrong?
Here is how I solved my problem:
On your spreadsheet go to the top menu > click Tool > then Script Editor and add the following scripts:
This is the script to call your data e.g. XML. Please update the script with your own information. YOUR-SHEET-NAME, is the tab name e.g. "prices".
function getData() {
var sheetName = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("YOUR-
SHEET-NAME");
var queryString = Math.random();
var cellFunction = '=ImportXML("https://yoururl-xml.php","//trade/price")';
var range = sheetName.getRange('A2');
range.clearContent(); // You can also use range.setFormula("");
SpreadsheetApp.flush();
range.setFormula(cellFunction);
}
Below that script, add the following script, more information on this page: Periodically refresh IMPORTXML() spreadsheet function
YOUR-SHEET-ID is the long number in the spreadsheet url e.g. 1YTB12xSTMSNdoT_S1U67MtOUDTf6n4OL2tJLnTNAXYZ
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var id = "YOUR-SHEET-ID";
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheetByName("YOUR-SHEET-NAME");
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var content = "";
var now = new Date();
var time = now.getTime();
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
var re2 = /((\?|&)(update=[0-9]*))/gi;
var re3 = /(",)/gi;
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
content = formulas[row][col];
if (content != "") {
var match = content.search(re);
if (match !== -1 ) {
// import function is used in this cell
var updatedContent = content.toString().replace(re2,"$2update=" + time);
if (updatedContent == content) {
// No querystring exists yet in url
updatedContent = content.toString().replace(re3,"?update=" + time + "$1");
}
// Update url in formula with querystring param
sheet.getRange(row+1, col+1).setFormula(updatedContent);
}
}
}
}
// Done refresh; release the lock.
lock.releaseLock();
}
Here is a screenshot of both scripts:
Then add the timer, go to the top menu click on the clock and add trigger. Make sure to select the right function i.e. RefreshImports.
Done!
I have two scripts:
One which adds an IMPORTJSON sheets function from bradjasper, works great.
The second one is that I want to refresh the scripts automatically, that does not work. The script does work when I ran it manually, and also the trigger does work according to the logs, but the data does not get refreshed.
I have tried all the different scripts, they do work, but the data imported on the sheet is not actually updated.
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var id = "1Vt-rqQZ7iXsui8Nrr2XABusd4lUbGqxj4HkzsRkZFNA";
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheetByName("Blad2");
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var content = "";
var now = new Date();
var time = now.getTime();
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|json|range)\(.*/gi;
var re2 = /((\?|&)(update=[0-9]*))/gi;
var re3 = /(",)/gi;
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
content = formulas[row][col];
if (content != "") {
var match = content.search(re);
if (match !== -1 ) {
// import function is used in this cell
var updatedContent = content.toString().replace(re2,"$2update=" + time);
if (updatedContent == content) {
// No querystring exists yet in url
updatedContent = content.toString().replace(re3,"?update=" + time + "$1");
}
// Update url in formula with querystring param
sheet.getRange(row+1, col+1).setFormula(updatedContent);
}
}
}
}
// Done refresh; release the lock.
lock.releaseLock();
// Show last updated time on sheet somewhere
sheet.getRange(1,1).setValue("Rates were last updated at " + now.toLocaleTimeString())
}
This script does run, as there are no errors in the logs. However, the data shown on the sheet does not change to reflect the current information from the API / JSON file.
This question already has answers here:
Periodically refresh IMPORTXML() spreadsheet function
(4 answers)
Closed last month.
I'm trying to get a Google sheets apps script to work for an IMPORTXML I'm using.
A1
=importxml("http://www.nfl.com/liveupdate/scorestrip/ss.xml","//#q")
A2
=importxml("http://www.nfl.com/liveupdate/scorestrip/ss.xml","//#h")
The data fills from A1:B16
According to a script I found on web to have it auto refresh:
function getData() {
var queryString = Math.random();
var cellFunction1 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('A1').getValue() + '?' + queryString + '","'+ SpreadsheetApp.getActiveSheet().getRange('A2').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('C1').setValue(cellFunction1);
var cellFunction2 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('A4').getValue() + '?' + queryString + '","'+ SpreadsheetApp.getActiveSheet().getRange('A5').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('C2').setValue(cellFunction2);
}
I don't know what I'm supposed to be putting/replacing in that code with mine. If someone could help me to explain what I'm supposed to be changing to get it to work in my sheet/provide some examples of how one might look that would be a huge help.
I appreciate
You can update the function by using getFormula() then setFormula() in a time-driven trigger function. Here is a code snippet from a related SO post:
/**
* Go through all sheets in a spreadsheet, identify and remove all spreadsheet
* import functions, then replace them a while later. This causes a "refresh"
* of the "import" functions. For periodic refresh of these formulas, set this
* function up as a time-based trigger.
*
* Caution: Formula changes made to the spreadsheet by other scripts or users
* during the refresh period COULD BE OVERWRITTEN.
*
* From: https://stackoverflow.com/a/33875957/1677912
*/
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
// At this point, we are holding the lock.
var id = "YOUR-SHEET-ID";
var ss = SpreadsheetApp.openById(id);
var sheets = ss.getSheets();
for (var sheetNum=0; sheetNum<sheets.length; sheetNum++) {
var sheet = sheets[sheetNum];
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var tempFormulas = [];
for (var row=0; row<formulas.length; row++) {
for (col=0; col<formulas[0].length; col++) {
// Blank all formulas containing any "import" function
// See https://regex101.com/r/bE7fJ6/2
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
if (formulas[row][col].search(re) !== -1 ) {
tempFormulas.push({row:row+1,
col:col+1,
formula:formulas[row][col]});
sheet.getRange(row+1, col+1).setFormula("");
}
}
}
// After a pause, replace the import functions
Utilities.sleep(5000);
for (var i=0; i<tempFormulas.length; i++) {
var cell = tempFormulas[i];
sheet.getRange( cell.row, cell.col ).setFormula(cell.formula)
}
// Done refresh; release the lock.
lock.releaseLock();
}
}
Hope this helps.
I have been working on something related and finally solved it by using code from this StackOverFlow Post, but I needed to add some extra bits.
It wasn't working well for me, so I made some changes and added extra logging to make it understandable for me. Here is goes:
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var now = new Date();
// Show start time on log
Logger.log("Starting Running at " + now.toLocaleTimeString());
var url = "URL OF YOUR SHEET";
var sheetName = "NAME OF YOUR SHEET";
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName(sheetName);
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var tempFormulas = [];
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
// Blank all formulas containing any "import" function
// See https://regex101.com/r/bE7fJ6/2
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
if (formulas[row][col].search(re) !== -1 ) {
tempFormulas.push({row:row+1,
col:col+1,
formula:formulas[row][col]});
sheet.getRange(row+1, col+1).setFormula(""); //cleans up the formula
}
}
}
// After a pause, replace the import functions
Utilities.sleep(500);
for (var i=0; i<tempFormulas.length; i++) {
var cell = tempFormulas[i];
sheet.getRange( cell.row, cell.col ).setFormula(cell.formula);
var nowLogger = new Date();
Logger.log("Update import from row " + cell.row + " col " + cell.col + " done at " + nowLogger.toLocaleTimeString());
Utilities.sleep(1000); //adding to try to control the amount of parallel connections from the Sheet
}
// Show Finished time on log
var now = new Date();
Logger.log("Sources from URLs were last updated at " + now.toLocaleTimeString());
// Done refresh; release the lock.
lock.releaseLock();
}
Then I added the Time-Based trigger from my project in https://script.google.com/home/triggers, and that one handles the automatic update execution.
I hope this helps!
It is actually not working. I mean it really does not refresh the cell whereas using a random number it works:
function getData() {
var queryString = Math.random();
var Xpath_1 = "/html/body/text()";
var importXpath_1 = '=IMPORTXML("' + 'http://www.pde-racing.com/trams/tram.php?id=999&valor=1&manega=Entrenament+1&dbTemps=Event999Ex.scdb&dbInscrits=Event999.scdb&name=&_=1616318271525' + '?' + queryString + '";"'+ Xpath_1 + '")';
SpreadsheetApp.getActiveSheet().getRange('B39').setValue(importXpath_1);
}
I've been reading up on how to save a spreadsheet to PDF via Google Docs Scripting. Most suggestions I've come across reference using something like:
theOutputFile.saveAndClose();
DocsList.createFile(theOutputFile.getAs('application/pdf')).rename(theOutputName+".pdf");
That is, they reference the saveAndClose() function. I don't want to save or close my spreadsheet - but I do want to download the current sheet as a PDF.
Any suggestions? Thanks.
For saving the current sheet as a PDF, you can hide all the other sheets, save the current, & then show all sheets again.
The pdf creation might start before the end of the sheets' hiding and then will include 2 sheets - the current & the last sheets - in the pdf file.
Adding a sleep or a confirmation msgbox, between showOneSheet & createPdf eliminated the problem.
This answer is a variation of Marco Zoqui's answer: "To send a single sheet you may hide all other before sending" in Google Apps Script to Email Active Spreadsheet
var sheet = SpreadsheetApp.getActiveSheet();
var sheetToSave = sheet.getName();
showOneSheet(sheetToSave);
Utilities.sleep(2000);
createPdf("TestFolder", "TestPDF");
showAllSheets();
function showOneSheet(SheetToShow) {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for(var i in sheets){
if (sheets[i].getName()==SheetToShow){
sheets[i].showSheet();
}
else {
sheets[i].hideSheet();
}
}
}
function showAllSheets() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for(var i in sheets){
sheets[i].showSheet();
}
}
function createPdf(saveToFolder, fileName){
var ssa = SpreadsheetApp.getActiveSpreadsheet();
var pdf = ssa.getAs("application/pdf");
try {
var folder = DocsList.getFolder(saveToFolder);
}
//Create Folder if not exists
catch(error){
folder = DocsList.createFolder(saveToFolder);
}
var file = folder.createFile(pdf);
file.rename(fileName);
return file;
}
I was able to get it to work using #hsgv's answer, however, this is the version I ended up using based on this.
// global save to folder variable:
var folderName = "My/Special/Folder";
function createInvoiceInGoogleDrive(){
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
// getting some values from the spreadhseet for the file name
var invoiceNumber = sheet.getRange("E3").getValue();
var vendor = sheet.getRange("A9").getValue();
var fileName = invoiceNumber + ' - ' + vendor + " - Invoice.pdf";
var pdfBlob = sheetToPDF(spreadsheet, sheet);
pdfBlob.setName(fileName);
var folder = getOrCreateFolder(folderName);
var matchingFileList = folder.find(fileName);
if ( matchingFileList.length > 0 ) {
Browser.msgBox("ERROR: New invoice not created. " + fileName + " already exists at " + folderName);
return false;
} else {
var f = folder.createFile(pdfBlob);
spreadsheet.toast('Created a new invoice on Google Drive!');
return true;
}
}
// thanks: https://gist.github.com/gregorynicholas/9008572
function sheetToPDF(spreadsheet, sheet) {
var ssID = spreadsheet.getId();
var gid = sheet.getSheetId();
// &gid=x at the end of above url if you only want a particular sheet
var url2 = "http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=" + ssID +
"&gid=" + gid +
"&fmcmd=12&size=7&fzr=true&portrait=true&fitw=true&locale=en&gridlines=false&printtitle=false&sheetnames=false&pagenum=UNDEFINED&attachment=true";
// AUTH TOKEN required to access the UrlFetchApp call below. You can receive it
// from https://appscripts.appspot.com/getAuthToken
var AUTH_TOKEN = "{GET YOUR OWN AUTH TOKEN}";
var auth = "AuthSub token=\"" + AUTH_TOKEN + "\"";
var res = UrlFetchApp.fetch(url2, {headers: {Authorization: auth}}).getBlob();
return res;
}
/**
* Get or create a folder based on its name/path
*/
function getOrCreateFolder(folderName) {
try {
var theFolder = DocsList.getFolder(folderName);
} catch(error){
var theFolder = DocsList.createFolder(folderName);
}
return theFolder;