Initiate a download
Asked Answered
E

4

17

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.

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)
}
Enounce answered 29/11, 2013 at 8:28 Comment(3)
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?Ietta
Yeah but I only use it myself so it doesn't really matter. But thanks.Enounce
Anchor has been deprecated see my updated solution in [similar SO question][1] [1]: #14022097Horn
I
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 :-)

Ietta answered 29/11, 2013 at 11:11 Comment(6)
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 oneStefanistefania
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()));Enounce
That should be OK, does it fail for you? (and sorry for having make you confused, it was not my intention)Ietta
Anchor has been deprecated see my updated solution in [similar SO question][1] [1]: #14022097Horn
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 pageLovelace
Class UiApp was deprecated. Related UiApp has been deprecated. Please use HtmlService insteadGarrote
A
11

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>
Allow answered 13/10, 2021 at 0:49 Comment(0)
I
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 :-)

Ietta answered 29/11, 2013 at 11:11 Comment(6)
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 oneStefanistefania
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()));Enounce
That should be OK, does it fail for you? (and sorry for having make you confused, it was not my intention)Ietta
Anchor has been deprecated see my updated solution in [similar SO question][1] [1]: #14022097Horn
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 pageLovelace
Class UiApp was deprecated. Related UiApp has been deprecated. Please use HtmlService insteadGarrote
P
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:

Plethora answered 22/2, 2022 at 4:54 Comment(0)
M
0

Solution using data URI scheme without any additional clicking:

function downloadFile(fileName, mimeType, blob) {
  const htmlOutput = HtmlService.createHtmlOutput(`<!DOCTYPE html>
<html>
  <head>
    <script>
      window.onload = function() {
        const a = document.createElement("a");
        a.href = "data:${mimeType};base64,${Utilities.base64Encode(blob.getBytes())}";
        a.download = "${fileName}";
        a.click();
        google.script.host.close();
      };
    </script>
  </head>
  <body>
  </body>
</html>`);
  SpreadsheetApp.getUi().showModalDialog(htmlOutput.setWidth(1).setHeight(1), 'Downloading...');
}

Also specialized for text files with optional UTF-8 BOM:

function downloadTextFile(fileName, mimeType, textContent) {
  const utf8bom = true;
  const blob = Utilities.newBlob([
    ...(utf8bom ? new Uint8Array([0xEF, 0xBB, 0xBF]) : []),
    ...Utilities.newBlob(textContent, "utf-8")
    .getBytes()]);
  downloadFile(fileName, mimeType, blob);
}
Massive answered 24/1 at 13:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.