2

I want a Google Script that automatically exports the Spreadsheet to a .XLSX whenever there is an edit made, overwriting any previous versions. Using this answer as a template, I created the following code:

function onEdit() {
  var s = SpreadsheetApp.getActiveSheet();
  var r = s.getActiveCell();
  if( r.getColumn() != 1 ) { //checks the column
    var row = r.getRow();
    var time = new Date();
    time = Utilities.formatDate(time, "GMT-08:00 ", "MM/dd/yy, hh:mm:ss");
    SpreadsheetApp.getActiveSheet().getRange('A' + row.toString()).setValue(time); 

    var id = 'MY_SPREADSHEET_KEY'
    var url = 'https://docs.google.com/feeds/';
    var doc = UrlFetchApp.fetch(url+'download/spreadsheets/Export?key='+id+'&exportFormat=xls',
                                googleOAuth_('docs',url)).getBlob()
    DocsList.createFile(doc).rename('newfile.xls')
  };
 };

function googleOAuth_(name,scope) {
  var oAuthConfig = UrlFetchApp.addOAuthService(name);
  oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
  oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
  oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
  oAuthConfig.setConsumerKey('anonymous');
  oAuthConfig.setConsumerSecret('anonymous');
  return {oAuthServiceName:name, oAuthUseToken:"always"};
}

However, it doesn't seem to be exporting. OR, if it is exporting, I'm not sure where this is happening.

Any ideas?

Community
  • 1
  • 1
Parseltongue
  • 11,157
  • 30
  • 95
  • 160
  • The title and first paragraph says XLSX but the code use XLS. As the accepted answer code export to XLS looks that the question should be edited. By other hand DocList and oAuthConfig were deprecated. See [Apps Script Sunset Schedule](https://developers.google.com/apps-script/sunset) – Rubén Jul 03 '16 at 14:00

2 Answers2

4

Since, some of the script of Serge is no longer usable due to changes in Google's API, I am posting my script which basically exports the current spreadsheet to xlsx (please note that exporting to xls is not supported) and saves it to a folder called Exports. Prior to doing this, it deletes the previous xlsx file and keeps only the latest one, so that you don't need to count time or alter any cells:

function exportAsxlsx() {
  var spreadsheet   = SpreadsheetApp.getActiveSpreadsheet();
  var spreadsheetId = spreadsheet.getId()
  var file          = Drive.Files.get(spreadsheetId);
  var url           = file.exportLinks[MimeType.MICROSOFT_EXCEL];
  var token         = ScriptApp.getOAuthToken();
  var response      = UrlFetchApp.fetch(url, {
    headers: {
      'Authorization': 'Bearer ' +  token
    }
  });

  var blobs   = response.getBlob();
  var folder = DriveApp.getFoldersByName('Exports');
  if(folder.hasNext()) {
    var existingPlan1 = DriveApp.getFilesByName('newfile.xlsx');
    if(existingPlan1.hasNext()){
      var existingPlan2 = existingPlan1.next();
      var existingPlanID = existingPlan2.getId();
      Drive.Files.remove(existingPlanID);
    }
  } else {
    folder = DriveApp.createFolder('Exports');
  }
  folder = DriveApp.getFoldersByName('Exports').next();
  folder.createFile(blobs).setName('newfile.xlsx')
}

It also creates the specific folder if there isn't one. You can play with these commands and see how these classes work. Note that you will need to enable Drive API from both Resources -> Advanced Google Services -> Drive API by switching it to on and also from Google Developers Console (see detailed instructions here). I have also set a simple trigger that calls this function on each edit. This can be done by: Resources -> Current project's triggers -> Add a new trigger. You won't need any libraries to add.

mgus
  • 808
  • 4
  • 17
  • 39
2

It was not indeed ! probably because the oAuth function didn't get the right authorization and also because the simple onEdit is not allowed to do this kind of operation.

You'll have to create an installable trigger (menu>ressource>current trigger>create).

Try this script below and run the authorize function.

I changed also a few details : timeZone is taken directly from the spreadsheet and id is also taken from the active spreadsheet.

Note also that the newly created XLSX will not overwrite any previous file, you'll get many documents with the same name ! If you want to keep only the latest version then you should take care of that by yourself, getting all the docs names 'new file' and delete them using file.setTrashed(true) before you create the new one.

This would be as simple as these 2 lines of code :

var oldVersions = DocsList.find('newfile.xls');
for(var d in oldVersions){oldVersions[d].setTrashed(true)};

The code :

function myOnEdit() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var s = ss.getActiveSheet();
  var r = s.getActiveCell();
  if( r.getColumn() != 1 ) { //checks the column
    var row = r.getRow();
    var time = new Date();
    time = Utilities.formatDate(time, ss.getSpreadsheetTimeZone(), "MM/dd/yy, hh:mm:ss");
    var id = ss.getId();
    s.getRange('A' + row.toString()).setValue(time); 
    var url = 'https://docs.google.com/feeds/';
    var doc = UrlFetchApp.fetch(url+'download/spreadsheets/Export?key='+id+'&exportFormat=xls',
                                googleOAuth_('docs',url)).getBlob()
    DocsList.createFile(doc).rename('newfile.xls')  
  }
}

function authorise(){
  // function to call to authorize googleOauth
  var id=SpreadsheetApp.getActiveSpreadsheet().getId();
  var url = 'https://docs.google.com/feeds/';
  var doc = UrlFetchApp.fetch(url+'download/spreadsheets/Export?key='+id+'&exportFormat=xls',
                            googleOAuth_('docs',url)).getBlob()
}
function googleOAuth_(name,scope) {
  var oAuthConfig = UrlFetchApp.addOAuthService(name);
  oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
  oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
  oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
  oAuthConfig.setConsumerKey('anonymous');
  oAuthConfig.setConsumerSecret('anonymous');
  return {oAuthServiceName:name, oAuthUseToken:"always"};
}

EDIT : following your comment, here is a version that saves only every 30 sec (or more if no edit are done). You can easily change the time value to another interval if necessary.

Re-run the authorize function to initialize the scriptProperty.

function myOnEdit() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var s = ss.getActiveSheet();
  var r = s.getActiveCell();
  if( r.getColumn() != 1 ) { //checks the column
    var row = r.getRow();
    var time = new Date();
    time = Utilities.formatDate(time, ss.getSpreadsheetTimeZone(), "MM/dd/yy, hh:mm:ss");
    var id = ss.getId();
    s.getRange('A' + row.toString()).setValue(time); 
    var lastSaveTime = new Date(Utilities.jsonParse(ScriptProperties.getProperty('exportTime')));
    var now = new Date().getTime();
    Logger.log(now - lastSaveTime.getTime())
    if (now - lastSaveTime.getTime() > 60000){ // delete every minute
      var oldVersions = DocsList.find('newfile.xls');
      for(var d in oldVersions){oldVersions[d].setTrashed(true)};
    }
    if (now - lastSaveTime.getTime() > 30000){ // save every 30"
      var url = 'https://docs.google.com/feeds/';
      var doc = UrlFetchApp.fetch(url+'download/spreadsheets/Export?key='+id+'&exportFormat=xls',
                                  googleOAuth_('docs',url)).getBlob()
      DocsList.createFile(doc).rename('newfile.xls')  
      ScriptProperties.setProperty('exportTime',Utilities.jsonStringify(new Date()));
    }
  }
}

function authorise(){
  // function to call to authorize googleOauth + initialize the TIMER
  ScriptProperties.setProperty('exportTime',Utilities.jsonStringify(new Date()));
  var id = SpreadsheetApp.getActiveSpreadsheet().getId();
  var url = 'https://docs.google.com/feeds/';
  var doc = UrlFetchApp.fetch(url+'download/spreadsheets/Export?key='+id+'&exportFormat=xls',
                                googleOAuth_('docs',url)).getBlob()
}

function googleOAuth_(name,scope) {
  var oAuthConfig = UrlFetchApp.addOAuthService(name);
  oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
  oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
  oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
  oAuthConfig.setConsumerKey('anonymous');
  oAuthConfig.setConsumerSecret('anonymous');
  return {oAuthServiceName:name, oAuthUseToken:"always"};
}
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Serge insas
  • 45,904
  • 7
  • 105
  • 131
  • You are some kind of Google-App-Script god. Thank you very much. It appears as if the script runs quite slowly when it saves each time, so I think I'll modify it so that it auto-saves an excel sheet every 30 seconds, and put the "Column Modified" function in a separate OnEdit script. – Parseltongue Oct 16 '13 at 10:09
  • So, I removed the "Column Modified" portion of the onEachEdit function, and had the "save to .XLS" feature run a trigger than runs every minute. Now when I run authorise I get the following error: `Request failed for returned code 404. Truncated server response: – Parseltongue Oct 16 '13 at 10:22
  • :-) thanks :-) that's probably a good idea, you could add a second condition in the script to save only if 30 minutes have elapsed since the last export. Use ScriptProperties to save the time value for example. Also please consider accepting the answer. – Serge insas Oct 16 '13 at 10:26
  • About the error, there was an error indeed in the authorize code... format was set to html and not xls... sorry, updated in both version – Serge insas Oct 16 '13 at 11:03
  • Works well. I'm trying to add your 'delete extra copies' function to a minute timer as well, so I set up a trigger to run the function every minute. But it doesn't appear to be working. Also is there anyway to make it to where it deletes all but the most recent excel document? – Parseltongue Oct 16 '13 at 11:11
  • not very simply if it has to be separate... maybe you could start a new thread on that part ? this one is getting long :-) but it could be integrated with just a second condition with time difference = 60000... By principle it will then never delete the newly created one since it is created in the same function scope - code updated – Serge insas Oct 16 '13 at 11:47
  • This is an obsolete answer imo. When run, this error is thrown *Script is using OAuthConfig which has been shut down. Learn more at goo.gl/IwCSaV ...* – Christiaan Westerbeek Oct 08 '15 at 13:58
  • @ Christiaan Westerbeek same comment, this answer was posted 2 years ago, before new version of spreadsheet. Your down vote means nothing but inappropriate understanding of SO rules (imo) and it won't change my life anyway. – Serge insas Oct 20 '15 at 11:19