0

I've successfully learnt through this site how to send a sheet as a PDF attached via google sheets. What I'd now like to do is slightly expand that a bit.

I want to select a constant specific range that will export, along with the last added row within a column range.

J1:S1 to export every time. then the newest row from J2:S2 onwards.

I like to have these two ranges combined, with the constant range (J1:S1) always being on top.

I'm not entirely sure if this is possible as a PDF or if it may need to be a HTML table. I can work with either atm.

I really not sure where to start here, so would appreciate suggestions.

Thank you!

enter image description here

In the example above, the titles remain on every email / PDF. But each newest edition to the rows below is added to the PDF. So in the example above the row with the 2's would be added.

var ss = SpreadsheetApp.getActiveSpreadsheet();

function sendReport() {
  var sheetTabNameToGet = "Form response master";
  var range = "J1:S1";
  var pdfBlob = exportRangeToPDf(range, sheetTabNameToGet);
  var message = {
    to: "example@example.com",
    subject: "Monthly sales report",
    body: "Hi team,\n\nPlease find the monthly report attached.\n\nThank you,\nBob",
    name: "Bob",
    attachments: [pdfBlob.setName("Monthly sales report")]
  }
  MailApp.sendEmail(message);
}


function exportRangeToPDf(range, sheetTabNameToGet) {
  var blob,exportUrl,options,pdfFile,response,sheetTabId,ssID,url_base;
  ssID = ss.getId();
  sh = ss.getSheetByName(sheetTabNameToGet);
  sheetTabId = sh.getSheetId();
  url_base = ss.getUrl().replace(/edit$/,'');
  exportUrl = url_base + 'export?exportFormat=pdf&format=pdf' +
    
    '&gid=' + sheetTabId + '&id=' + ssID +
      '&range=' + range + 
        '&size=A4' +     // paper size
          '&portrait=false' +   // orientation, false for landscape
            '&fitw=true' +       // fit to width, false for actual size
              '&sheetnames=true&printtitle=false&pagenumbers=true' + //hide optional headers and footers
                '&gridlines=false' + // hide gridlines
                  '&fzr=false';       // do not repeat row headers (frozen rows) on each page
  
  options = {
    headers: {
      'Authorization': 'Bearer ' +  ScriptApp.getOAuthToken(),
    }
  }
  options.muteHttpExceptions = true;//Make sure this is always set
  response = UrlFetchApp.fetch(exportUrl, options);
  if (response.getResponseCode() !== 200) {
    console.log("Error exporting Sheet to PDF!  Response Code: " + response.getResponseCode());
    return;
    
  }  
  blob = response.getBlob();
  return blob;
}
comiconor
  • 71
  • 5
  • Hi, I've added what I have so far. It successfully adds the first data range I want into the PDF. But I have no idea how to add a range into that for the newest row from J2:S2 onwards. – comiconor Jul 29 '22 at 20:16
  • What do you mean by the "the newest row from J2:S2 onwards"? Please add some sample input data and the corresponding expected result – Rubén Jul 29 '22 at 20:20
  • So when new data populated within rows within J:S it is added onto the PDF. The newest data is added into downwards trajectory – comiconor Jul 29 '22 at 20:28
  • Hide all the rows between J1:S1 and the "newest row" and all the rows after the "newest row" then row your script – Rubén Jul 29 '22 at 20:31
  • I've added more to my og comment, to see if that helps. I did think simply hiding the rows may work. But ideally I wanted this to run on a trigger without intervention – comiconor Jul 29 '22 at 20:35
  • You can make that the script do the hide/unhide work. – Rubén Jul 29 '22 at 20:41
  • How would that work? so essentially it would hide all rows except the most recent one? – comiconor Jul 29 '22 at 20:44
  • Another way is to use the spredsheet form submission event object, more specifically the values property of this event to update a sheet having only the headers. This will be a way more efficient. – Rubén Jul 29 '22 at 21:03
  • if your data comes from a form, you should sort using onFormSubmit to get the new row at the beginning (i.e. row #2). – Mike Steelson Jul 30 '22 at 04:53

1 Answers1

1

If the last response is always a last row, you can remove all the rows between the header and the last row, export the sheet in PDF, send it, and restore the sheet back:

function main() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sh = ss.getSheetByName("Form response master");

  var formula = sh.getRange('a1').getFormula();    // take the formula from A1
  var data = sh.getDataRange().getDisplayValues(); // get data from the original table
  var table = [data.shift(), data.pop()];          // make the new table from first and last row

  sh.clearContents()                               // clear the sheet
    .getRange(1,1,table.length,table[0].length)
    .setValues(table);                             // put the new table on the sheet

  sendReport(get_blob_PDF(ss, sh));                // make PDF and send it
  sh.clearContents().getRange('a1')
   .setFormula(formula);                           // restore the sheet
}

function get_blob_PDF(ss, sh) {
  SpreadsheetApp.flush(); // update the Sheet
  var ssID = ss.getId();
  var shID = sh.getSheetId();
  var url_base = ss.getUrl().replace(/edit$/,'');
  var exportUrl = url_base + 'export?exportFormat=pdf&format=pdf' +
    '&gid=' + shID + '&id=' + ssID +
    '&size=A4' +         // paper size
    '&portrait=false' +  // orientation, false for landscape
    '&fitw=true' +       // fit to width, false for actual size
    '&sheetnames=true&printtitle=false&pagenumbers=true' + //hide optional headers and footers
    '&gridlines=false' + // hide gridlines
    '&fzr=false';        // do not repeat row headers (frozen rows) on each page
  
  var options = {headers: {'Authorization': 'Bearer ' +  ScriptApp.getOAuthToken()}}
  options.muteHttpExceptions = true; // Make sure this is always set

  var response = UrlFetchApp.fetch(exportUrl, options);
  if (response.getResponseCode() !== 200) {
    console.log("Error exporting Sheet to PDF!  Response Code: " + response.getResponseCode());
    return;  
  }

  return response.getBlob();
}

function sendReport(pdfBlob) {
  var message = {
    to: "exmaple@mail.com",
    subject: "Monthly sales report",
    body: "Hi team,\n\nPlease find the monthly report attached.\n\nThank you,\nBob",
    name: "Bob",
    attachments: [pdfBlob.setName("Monthly sales report")]
  }
  MailApp.sendEmail(message);
}

In your particular case the problem was that your sheet 'Form response master' is made via the array formula in cell 'A1'. It makes the code a little bit weird.

It does the job but, yeah, it's likely a script that takes a response object from the trigger onFormSubmit(e) would be more efficient.

Update

If there is email address in cell 'C2' you can take it if you replace the line:

sendReport(get_blob_PDF(ss, sh));

with:

var address = table[1][2]; // get the address from 'C2'
sendReport(get_blob_PDF(ss, sh), address);

And use the address in the function sendReport() if you change these lines:

function sendReport(pdfBlob) {
  var message = {
    to: "exmaple@mail.com",

this way:

function sendReport(pdfBlob, address) {
  var message = {
    to: address,

Update 2

If the cell 'C2' contains a name and you have the exact matching between every name and email, you can get the email address by the name via object: {name1: address1, name2: address2, ...etc}. To make this happen you can change the first lines of the function sendReport() this way:

function sendReport(pdfBlob, person) {
  var address = {
    'James Smith': 'james.smith@gmail.com',
    'Mark Williams': 'mark.willaims@gmail.com',
    'Vladimir Putin': 'goto@thehell.now',
  }
  var message = {
    to: address[person],

Technically it would be enough, but just in case I'd advice to change those two lines in the main() function as well:

var person = table[1][2]; // get the persons name from 'C2'
sendReport(get_blob_PDF(ss, sh), person);

Update 3 (XLSX and CSV)

To send it as XLSX file you can use this function:

function get_blob_XLSX(ss, sh) {
  SpreadsheetApp.flush(); // reload the Sheeet
  var ssID = ss.getId();
  var shID = sh.getSheetId();
  var url = 'https://docs.google.com/spreadsheets/d/' + ssID + '/export?format=xlsx&gid=' + shID;
  
  var options = {headers: {'Authorization': 'Bearer ' +  ScriptApp.getOAuthToken()}}
  options.muteHttpExceptions = true; // Make sure this is always set

  var response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() !== 200) {
    console.log("Error exporting Sheet to XLS!  Response Code: " + response.getResponseCode());
    return;  
  }

  return response.getBlob();
}

To send it as a CSV file here you go:

function get_blob_CSV(ss, sh) {
  SpreadsheetApp.flush(); // reload the Sheeet
  var ssID = ss.getId();
  var shID = sh.getSheetId();
  var url = 'https://docs.google.com/spreadsheets/d/' + ssID + '/export?format=csv&gid=' + shID;
  
  var options = {headers: {'Authorization': 'Bearer ' +  ScriptApp.getOAuthToken()}}
  options.muteHttpExceptions = true; // Make sure this is always set

  var response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() !== 200) {
    console.log("Error exporting Sheet to XLS!  Response Code: " + response.getResponseCode());
    return;  
  }

  return response.getBlob();
}
Yuri Khristich
  • 13,448
  • 2
  • 8
  • 23
  • This is fantastic. Thank you so much! – Each new row contains a name. How would I change this code so that it emails to the person whose name is in that row? The names are always within the ‘C’ column. I’m assuming it would be lots of IF statements with the relevant email address against the name, but I’m not too sure. – comiconor Jul 30 '22 at 21:28
  • "_emails to the person whose name is in that row_" — if the cell contains an email address, it can be done pretty easy. But if it contains just a name... I don't know how you could obtain email address from a name. – Yuri Khristich Jul 30 '22 at 21:44
  • See the update, how to get the address and use it in the function `sendReport()` – Yuri Khristich Jul 30 '22 at 22:20
  • The cell doesn't contain an email address, just a name. I would want to define the address to send it to via the script. So say for example the new row in column C contained the name 'James Smith', the script contains a statement that if the newest row within column C = 'James Smith', send the email to 'james.smith@gmail.com'. But if the latest row within column C had name 'Mark Williams' it would send to 'mark.willaims@gmail.com'....There will only be 10 names within that list. So it would be 10 email addresses that would need to be defined. Is that possible? – comiconor Jul 30 '22 at 22:50
  • 1
    See my second update. – Yuri Khristich Jul 30 '22 at 23:06
  • 1
    Your sir are fantastic!! - That has worked perfectly. Thank you so much for the help. Beyond amazing. Helped me with my project & understand the code much more. - Nice touch with the Putin email btw haha. – comiconor Jul 30 '22 at 23:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/246930/discussion-between-comiconor-and-yuri-khristich). – comiconor Jul 31 '22 at 11:19