15

I added a new menu item to my spreadsheet using google apps script. This menu item creates a file, but I'd like for it to initiate the download of the file after creating it.

Is this possible?

Remember, this is not a web app, but a menu item in my spreadsheet.

Thanks

Edit:

Thanks to Serge insas' suggestion, the following simple script works perfectly, and opens a download window with the link I need:

function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var csvMenuEntries = [ {name: "Zip all CSVs", functionName: "saveAsCSV"} ];
  ss.addMenu("CSV", csvMenuEntries);
};

function saveAsCSV() {
  var folder = createCSVs(); // creates a folder with CSV for each Sheet
  var zipFile = zipCSVs(folder, "DI.zip"); // creates a zip of all CSVs in folder

  var ui = UiApp.createApplication().setTitle("Download");
  var p = ui.createVerticalPanel();
  ui.add(p);
  p.add(ui.createAnchor("Download", zipFile.getDownloadUrl()));
  SpreadsheetApp.getActive().show(ui)
}
tbkn23
  • 5,205
  • 8
  • 26
  • 46
  • If I may, I think the setWidth and setHeight are really useful in such a UI because it's a bit empty isn't it? – Serge insas Nov 29 '13 at 16:14
  • Yeah but I only use it myself so it doesn't really matter. But thanks. – tbkn23 Nov 29 '13 at 16:24
  • Anchor has been deprecated see my updated solution in [similar SO question][1] [1]: http://stackoverflow.com/questions/14021597/how-to-download-created-csv-file-using-google-apps-script/31973529#31973529 – vish Aug 12 '15 at 19:12

3 Answers3

10

EDIT : read the comments below, Zig Mandel is perfectly right when he points out the limitations of the "complicated" version, it was really a simple (and fun) exercice to show other methods.


I think you'll have to use an intermediate Ui as a popup to confirm the download. After that there are 2 possible ways that I know, one is very simple and the other is quite cumbersome, make your choice, the code below shows both of them.

note : to use the complicated one you need to deploy your app (ie save a version and deploy as webapp), for the simple one just use it "as it is". (I show the simple in the code comments).

The code :

function onOpen() {
  var menuEntries = [ {name: "test download", functionName: "downloadFile"}
                     ];
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  sheet.addMenu("Utils",menuEntries);
}

function downloadFile(){
  var file = DriveApp.createFile('test file', 'Some content in this file to test it');
  var fileID = file.getId();
  var fileName = file.getName();
  var ui = UiApp.createApplication().setTitle('Download');
  var url = ScriptApp.getService().getUrl()+'?&ID='+fileID+'&name='+fileName;
  var p = ui.createVerticalPanel();
  ui.add(p);
  p.add(ui.createAnchor('click to download', url));
  p.add(ui.createAnchor('or use this link ',file.getDownloadUrl()));// this is the simple one, just get the file you created and use getDownloadUrl()
  SpreadsheetApp.getActive().show(ui)
}

function doGet(e){
  var fileId = e.parameter.ID;
  var fileName = e.parameter.name;
  var fileString = DocsList.getFileById(fileId).getContentAsString();
  return ContentService.createTextOutput(fileString).downloadAsFile(fileName);
}

PS : I had some fun writing this, the "complicated version" is really funny imho :-)

Serge insas
  • 45,904
  • 7
  • 105
  • 131
  • 1
    The first version is good. There is no need to use the second version plus it only works for text files. User could be creating anything like a pdf (thou you could improve it so it serves the file blob instead of its text content), but you will not have any advantage over the fisrt one – Zig Mandel Nov 29 '13 at 13:36
  • Hmm, managed to get confused from all the methods, I'm not very familiar with google apps script APIs... I tried this but nothing happens: var ui = UiApp.createApplication().setTitle("Download"); var p = ui.createVerticalPanel(); ui.add(p); p.add(ui.createAnchor("Download", zipFile.getDownloadUrl())); – tbkn23 Nov 29 '13 at 16:02
  • That should be OK, does it fail for you? (and sorry for having make you confused, it was not my intention) – Serge insas Nov 29 '13 at 16:05
  • 1
    Anchor has been deprecated see my updated solution in [similar SO question][1] [1]: http://stackoverflow.com/questions/14021597/how-to-download-created-csv-file-using-google-apps-script/31973529#31973529 – vish Aug 12 '15 at 19:13
  • And also `file.getDownloadUrl()` return url in form `https://doc-0o-0o-docs.googleusercontent.com/docs/securesc/heb9rsi6d8r2pe9n5dqucm4ou0i4lsbm/gib2on90e57kjlg6aqe0v1iqcln67ale/1509372000000/06502083857216782840/06502083857216782840/0B8Ow9luRLALALARZeWI3NDA?e=download&gd=true` which points to empty page – Dimitry K Oct 30 '17 at 15:48
  • Class UiApp was deprecated. Related [UiApp has been deprecated. Please use HtmlService instead](https://stackoverflow.com/q/54605042/1595451) – Rubén Nov 28 '22 at 00:59
9

OP's answer is deprecated (in 2021), so I made a more general purpose one based on it.


Code.gs:

// Runs when the spreadsheet starts, adds a tab at the top
function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Script Menu')
    .addItem('Download a file!', 'dlFile')
    .addToUi();
}

// Run when you click "Download a file!"
function dlFile() {
  let file = DriveApp.getRootFolder().createFile('Hi.txt', 'Hello, world!');

  // Create little HTML popup with the URL of the download
  let htmlTemplate = HtmlService.createTemplateFromFile('Download.html');
  htmlTemplate.dataFromServerTemplate = { url: file.getDownloadUrl() };

  let html = htmlTemplate
    .evaluate()
    .setWidth(400)
    .setHeight(300);

  SpreadsheetApp.getUi()
    .showModalDialog(html, 'Download');
};


Download.html:

<!DOCTYPE html>
<html>
  <head>
    <script>
      let data = <?!= JSON.stringify(dataFromServerTemplate) ?>; // Stores the data directly in the javascript code

      function downloadFile() {
        document.getElementById("dlBtn").innerText = "Downloading..";

        window.open(data.url, '_blank');

        document.getElementById("dlBtn").disabled = true;
      }
    </script>
  </head>
  <body>
    <button id="dlBtn" onclick="downloadFile()">Download</button>
  </body>
</html>
Rubén
  • 34,714
  • 9
  • 70
  • 166
Dr-Bracket
  • 4,299
  • 3
  • 20
  • 28
0

Just adding to @dr-bracket's answer where I made some small additions to the scripts in an attempt to stop the browser from navigating away to a new tab.

I got the idea from:

Download a created Google Doc from a deployed web app (Google Apps Script)

Where @tanaike uses the google.script.run.withSuccessHandler class and method to create a popup prompt then closes and returns to your app on download. (May not popup if your browser settings are set to not pick download location.)

Code.gs:

// Runs when the spreadsheet starts, adds a tab at the top
function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Script Menu')
    .addItem('Download a file!', 'dlFile')
    .addToUi();
}

// Run when you click "Download a file!"
function dlFile() {
  let file = DriveApp.getRootFolder().createFile('Hi.txt', 'Hello, world!');

  // Create little HTML popup with the URL of the download. Added filename to object. ~~~~~~~~~~~
  let htmlTemplate = HtmlService.createTemplateFromFile('Download.html');
  htmlTemplate.dataFromServerTemplate = { url: file.getDownloadUrl(), name: file.getName() };

  let html = htmlTemplate
    .evaluate()
    .setWidth(400)
    .setHeight(300);

  SpreadsheetApp.getUi()
    .showModalDialog(html, 'Download');
};

// Added the following to satisfy the withSuccessHandler method: ~~~~~~~~~~~~~
function createDownloadUrl(data) {
  return { 
    url: data.url,
    name: data.name,
  };
}

Download.html:

<!DOCTYPE html>
<html>
  <head>
    <script>
      let data = <?!= JSON.stringify(dataFromServerTemplate) ?>; // Stores the data directly in
      // the javascript code

      function downloadFile() {
        const dlBtn = document.getElementById("dlBtn");
        dlBtn.innerText = "Downloading..";

        // window.open(data.url);
        // Replaced with: 
        // the url and name variables will be returned here from the 
        // code.gs function createDownloadEvent() after it runs successfully.
        google.script.run
          .withSuccessHandler(({ url, name }) => {
              const a = document.createElement("a");
              document.body.appendChild(a);
              a.download = name;
              a.href = url;
              a.target = "_blank";
              a.click();
            })
            .createDownloadEvent(data);

        dlBtn.disabled = true;
      }
    </script>
  </head>
  <body>
    <button id="dlBtn" onclick="downloadFile()">Download</button>
  </body>
</html>

Resources: