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)
}
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 :-)
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>
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:
https://developers.google.com/apps-script/guides/html/reference/run#withsuccesshandlerfunction
Download a created Google Doc from a deployed web app (Google Apps Script)
Related
I made a script using the advanced service API Google Drive. It's working fine when we stay into the app script GUI :
/** #OnlyCurrentDoc */
function GetListOfDrivesName() {
const ALL_AVALIBLE_DRIVE = Drive.Drives.list();
return ALL_AVALIBLE_DRIVE.items.map(driveData => driveData = driveData.name)
}
However, when it's called from cell into Google Sheet we got an error
Error message is like below : GoogleJsonResponseException: API call to drive.drives.list failed with error: Login Required.
I guess it's some authentification & authorisation to ask before using the sheet.
Unfortuantly I have no idea how to request that ! I read doc but it's sound like it will be asked promptly.
In addition, trigger have been added for this function but it neither worked
Any idea ? Thanks in advance !
Hi #Yuri Khristich you were right, using ui is a good work around , code turn up to work corectly
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
ui.createMenu('[FM Script for G Drive]')
.addItem('test','showAvaillableDrive')
.addToUi();
}
function showAvaillableDrive(){
var ui = SpreadsheetApp.getUi();
var resultat = ui.prompt(GetListOfDrivesName())
}
function GetListOfDrivesName() {
const ALL_AVALIBLE_DRIVE = Drive.Drives.list();
return ALL_AVALIBLE_DRIVE.items.map(driveData => driveData = driveData.name)
}
As suggested by the comments you can build a menu over the Sheet and run it to write down Drive files that I own.
I have this custom Sheet where you can run the "Drive" function over "Adv Menu"
I automatically get the information of the Sheet and get a list of my Drive files:
function onOpen () {
var ui= SpreadsheetApp.getUi();
ui.createMenu('Adv Menu').addItem('Drive', 'getMyFilesFromDrive').addToUi();
}
function getMyFilesFromDrive() {
var myFiles = DriveApp.searchFiles('"me" in owners');
var sheet = SpreadsheetApp.getActive().getSheetByName("Files");
sheet.clear();
var rows = [];
rows.push(["ID", "Name", "Url"]);
while(myFiles.hasNext()) {
var file = myFiles.next();
if(file != null) {
rows.push([file.getId(), file.getName(), file.getUrl()]);
}
}
sheet.getRange(1,1,rows.length,3).setValues(rows);
}
It would also write it directly to my Sheet. Feel free to review it and use it.
You can also Draw a button (Inserting a Google Drawing) over the Sheet and assign a script:
Reference:
https://developers.google.com/apps-script/guides/menus
I have a script that will run an HTTP request to our server and bring the most recent orders.
We want to be able to run the script on request but also be able to Install it as an addon to different sheets for our different stores
The front End of the app is generated by this html
<link href="https://ssl.gstatic.com/docs/script/css/add-ons.css"
rel="stylesheet">
<div class="sidebar">
<div class="block form-group">
<button class="blue" id="load_orders">Import Order Data</button>
</div>
<div id='orders'></div>
</div>
<script>
$(function onSuccess(load_orders) {
});
withSuccessHandler(onSuccess).importcogs();
});
</script>
Then on the .gs I have a script to show the app (before we Deploy it) and the import orders script
function onInstall() {
onOpen();
}
function onOpen() {
SpreadsheetApp.getUi()
.createAddonMenu() // Add a new option in the Google Docs Add-ons Menu
.addItem("Import Order Data", "showSidebar")
.addToUi(); // Run the showSidebar function when someone clicks the menu
}
function showSidebar() {
var html = HtmlService.createTemplateFromFile("Front")
.evaluate()
.setTitle("Import Order - Search"); // The title shows in the sidebar
SpreadsheetApp.getUi().showSidebar(html);
}
function importcogs() {
Logger.log("import begin");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var urlsheet = ss.getSheetByName("GetInfo");
var request = urlsheet.getRange(5,2).getValue();
Logger.log(request);
var response = UrlFetchApp.fetch(request);
Logger.log("download data finish");
Logger.log(response.getContentText());
var sheet = ss.getSheetByName("Data");
var obj = JSON.parse(response);
let vs = obj.data.map(o => Object.values(o));//data
vs.unshift(Object.keys(obj.data[0]));//add header
sheet.getRange(1,1,vs.length, vs[0].length).setValues(vs);//output to spreadsheet
}
I Haven't been able to link the "Import orders" button to the script for some reason .
As what Cooper has said in the above comment, it is not a standalone function:
Sample Usage:
google.script.run.withSuccessHandler(onSuccess).importcogs();
This will in return runs the importcogs function and will trigger onSuccess function if the function successfully finished. Parameters of the onSuccess will be the return of the function called importcogs if it has any.
Is there a way to create a copy to clipboard button in Sidebar using Google's Apps Script?
My current code is the following, but the copy button is not working:
function createCalendarEvent() {
var html = HtmlService.createHtmlOutput()
.setTitle("Πληροφορίες για Ημερολόγιο")
.setContent('<div><p id="item-to-copy">Test</p>' + '\n\n<button onclick='+"copyToClipboard()"+'>Copy</button></div>')
var ui = SpreadsheetApp.getUi(); // Or DocumentApp or SlidesApp or FormApp.
ui.showSidebar(html);
}
function copyToClipboard() {
const str = document.getElementById('item-to-copy').innerText;
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
The second function is javascipt.
Can you help me please?
Edit
When I click F12 on the browser, I get the following error:
Uncaught ReferenceError: copyToClipboard is not defined
at HTMLButtonElement.onclick (userCodeAppPanel:1)
onclick # userCodeAppPanel:1
Modification points:
From The second function is javascipt., in your script, if copyToClipboard() is put to the HTML file of the script editor, in that case, html in your script doesn't include the function. By this, such error occurs.
Or, if copyToClipboard() is put to the Google Apps Script file of the script editor, copyToClipboard() cannot be run from HTML side. By this, such error occurs.
In order to run copyToClipboard(), I would like to propose the following modification.
Modified script:
HTML&Javascript side:
Please copy and paste the following script to the HTML file of the script editor in Google Apps Script project. The filename is index.html.
<div>
<p id="item-to-copy">Test</p>
<button onclick="copyToClipboard()">Copy</button>
</div>
<script>
function copyToClipboard() {
const str = document.getElementById('item-to-copy').innerText;
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
</script>
Google Apps Script side:
Please copy and paste the following script to the Google Apps Script file of the script editor in Google Apps Script project.
function createCalendarEvent() {
var html = HtmlService.createHtmlOutputFromFile("index").setTitle("Πληροφορίες για Ημερολόγιο")
var ui = SpreadsheetApp.getUi(); // Or DocumentApp or SlidesApp or FormApp.
ui.showSidebar(html);
}
When createCalendarEvent() is run, the script loads the HTML & Javascript from index.html file.
Note:
If you want to use setContent, you can also use the following scripts.
HTML&Javascript side:
<script>
function copyToClipboard() {
const str = document.getElementById('item-to-copy').innerText;
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
</script>
Google Apps Script side:
function createCalendarEvent() {
var javascript = HtmlService.createHtmlOutputFromFile("index").getContent();
var htmlData = `<div><p id="item-to-copy">Test</p><button onclick="copyToClipboard()">Copy</button></div>${javascript}`;
var html = HtmlService.createHtmlOutput()
.setTitle("Πληροφορίες για Ημερολόγιο")
.setContent(htmlData)
var ui = SpreadsheetApp.getUi(); // Or DocumentApp or SlidesApp or FormApp.
ui.showSidebar(html);
}
References:
Class HtmlService
HTML Service: Create and Serve HTML
I'm appending a row in a spreadsheet from a form then taking the data that was added to the spreadsheet and populating a document template. From there I'm creating a PDF and emailing it to myself. The problem I'm facing is that the data is always coming from the second to last row of the spreadsheet instead of the newly appended row (from the latest form data). It seems like the appended data is not being saved before the AutoFillDocFromTemplate function runs. What am I missing?
function doGet(request) {
return HtmlService.createTemplateFromFile('Index').evaluate();
};
/* #Include JavaScript and CSS Files */
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
/* #Process Form */
function processForm(formObject) {
var url = "https://docs.google.com/spreadsheets/d/1nz2uIWab1eSirljzvNn6SyNyxz3npDTu4mqVYV0blsU/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Data");
ws.appendRow([formObject.company_name,
formObject.identity_transformation,
formObject.character_want,
formObject.external_problem,
formObject.internal_problem,
formObject.philisophical_problem,
formObject.empathy,
formObject.authority,
formObject.plan_step1,
formObject.plan_step2,
formObject.plan_step3,
formObject.direct_cta,
formObject.transitional_cta,
formObject.failure,
formObject.success]);
}
/* This function creates a new document from a template and updates the placeholder with info from a Google Sheet*/
function AutofillDocFromTemplate(){
// Get the spreadsheet & sheet
var url = "https://docs.google.com/spreadsheets/d/1nz2uIWab1eSirljzvNn6SyNyxz3npDTu4mqVYV0blsU/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url).getSheetByName("Data");
// Set the range to the last row of data in the Sheet
var data = ss.getRange(ss.getLastRow(),1,1, ss.getLastColumn()).getValues();
// Get the original template Doc
const templateDoc = DriveApp.getFileById("1yu5jzg4NbRtTy_UjwzBmnpc-3_pNOqA-l1_UVsiAIWQ");
// Get the folder for where the docs should go
const folder = DriveApp.getFolderById("1prOQxp5jmDvJqiwIfLbbkLYWoz5QlTUC");
// Create the new file name
const newFileName = ("BrandScript")
// Create a copy of the template doc
const newTempFile = templateDoc.makeCopy(newFileName, folder);
// Open the new temp doc
const openDoc = DocumentApp.openById(newTempFile.getId());
// Get the body of the new temp doc
const body = openDoc.getBody();
// Replace placeholders with spreadsheet data from last row
body.replaceText("%company_name%", data[0][0]);
body.replaceText("%identity_transformation%", data[0][1]);
body.replaceText("%character_want%", data[0][2]);
body.replaceText("%external_problem%", data[0][3]);
body.replaceText("%internal_problem%", data[0][4]);
body.replaceText("%philisophical_problem%", data[0][5]);
body.replaceText("%empathy%", data[0][6]);
body.replaceText("%authority%", data[0][7]);
body.replaceText("%plan_step1%", data[0][8]);
body.replaceText("%plan_step2%", data[0][9]);
body.replaceText("%plan_step3%", data[0][10]);
body.replaceText("%direct_cta%", data[0][11]);
body.replaceText("%transitional_cta%", data[0][12]);
body.replaceText("%failure%", data[0][13]);
body.replaceText("%success%", data[0][14]);
// Save and close the new doc
openDoc.saveAndClose();
//Send email with new document
var message = "Attached is your draft BrandScript"; // Customize message
var emailTo = "to be inserted" // replace with your email
var subject = "Your Draft BrandScript"; // customize subject
var pdf = DriveApp.getFileById(openDoc.getId()).getAs('application/pdf').getBytes();
var attach = {fileName:'DraftBrandScript.pdf',content:pdf, mimeType:'application/pdf'}; // customize file name: "Autogenerated template"
MailApp.sendEmail(emailTo, subject, message, {attachments:[attach]});
}
<script>
// Prevent forms from submitting.
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
function handleFormSubmit(formObject) {
google.script.run.processForm(formObject);
google.script.run.AutofillDocFromTemplate();
document.getElementById("myForm").reset();
}
</script>
I think that the reason of your issue is google.script.run works with the asynchronous process. By this, at the following script,
google.script.run.processForm(formObject);
google.script.run.AutofillDocFromTemplate();
Before processForm is not finished, AutofillDocFromTemplate is run. So in order to remove your issue, I would like to propose the following patterns.
Pattern 1:
In this pattern, withSuccessHandler is used. By this, AutofillDocFromTemplate is run after processForm was run.
From:
google.script.run.processForm(formObject);
google.script.run.AutofillDocFromTemplate();
To:
google.script.run.withSuccessHandler(() => google.script.run.AutofillDocFromTemplate()).processForm(formObject);
Pattern 2:
In this pattern, Google Apps Script is modified. By this, AutofillDocFromTemplate is run after processForm was run.
Google Apps Script side:
From:
function processForm(formObject) {
var url = "https://docs.google.com/spreadsheets/d/1nz2uIWab1eSirljzvNn6SyNyxz3npDTu4mqVYV0blsU/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Data");
ws.appendRow([formObject.company_name,
formObject.identity_transformation,
formObject.character_want,
formObject.external_problem,
formObject.internal_problem,
formObject.philisophical_problem,
formObject.empathy,
formObject.authority,
formObject.plan_step1,
formObject.plan_step2,
formObject.plan_step3,
formObject.direct_cta,
formObject.transitional_cta,
formObject.failure,
formObject.success]);
AutofillDocFromTemplate() // <--- Added
}
HTML&Javascript side:
google.script.run.processForm(formObject);
// google.script.run.AutofillDocFromTemplate(); // <--- removed
Note:
If the issue was not resolved by above modifications, please try to use SpreadsheetApp.flush().
Reference:
Class google.script.run
I'm trying to use google script to display a bunch of data in a HTML file, however, my data doesn't seem to make it to the HTML file and I have no idea why. Can someone please tell me what I'm missing here?
Path: htmlList.html
<!DOCTYPE html>
<html>
<head>
<base target="_top" />
</head>
<body>
My HTML page
<? for(var i = 0; i <= (users.length -1); i++) { ?>
<p><?= users[i].firstName ?></p>
<? } ?>
</body>
</html>
Path: Code.js
function doGet(users) {
var html = HtmlService.createTemplateFromFile("htmlList");
html.users = users;
return html.evaluate().setTitle("Test my app");
}
function generateLinks() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rr = spreadSheet.getLastRow();
var users = [];
for (var i = 3; i <= rr; i++) {
var firstName = spreadSheet.getRange(i, 1).getValue();
var user = {
firstName: firstName
};
users.push(user);
}
doGet(users);
}
You want to open new tab for own browser using the created HTML data, when you run the function at the script editor.
You are using the container-bound script.
If my understanding is correct, how about this modification?
Modification points:
In this modification, I used the following flow. Please think of this as just one of several answers.
By running runScript(), a dialog is opened.
The opened dialog runs a Javascript for opening new tab of the browser and open the URL of Web Apps.
At this time, generateLinks() is run from doGet(), and the values are retrieved and put to HTML data.
Close the dialog.
By this flow, when you run the function at the script editor, the created HTML is opened as new tab of your browser.
Modified script:
Please copy and paste the following script to the container-bound script of Spreadsheet. And then, please redeploy Web Apps as new version. At that time, as a test case, please set Execute the app as: and Who has access to the app: as Me and Anyone, even anonymous, respectively. In this case, you are not required to modify the script of HTML side.
function doGet() {
var html = HtmlService.createTemplateFromFile("htmlList");
html.users = generateLinks(); // Modified
return html.evaluate().setTitle("Test my app");
}
function generateLinks() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rr = spreadSheet.getLastRow();
var users = [];
for (var i = 3; i <= rr; i++) {
var firstName = spreadSheet.getRange(i, 1).getValue();
var user = {
firstName: firstName
};
users.push(user);
}
return users; // Modified
}
// I added the following function. Please run this function.
function runScript() {
var url = ScriptApp.getService().getUrl();
var script = "<script>window.open('" + url + "', '_blank').focus();google.script.host.close();</script>";
var html = HtmlService.createHtmlOutput(script);
SpreadsheetApp.getUi().showModalDialog(html, 'sample');
}
When var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); is used, the 1st sheet of Spreadsheet is used. So when you want to retrieve the values from the specific sheet, for example, please modify to SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheetName").
Note:
If you modified the script of Web Apps, please redeploy Web Apps as new version. By this, the latest script is reflected to the Web Apps. Please be careful this.
References:
HTML Service: Create and Serve HTML
HTML Service: Templated HTML
Taking advantage of Web Apps with Google Apps Script
Assuming that your data on the spreadsheet looks something like this -
And the desired output looks something like this (you're free to modify the CSS in your .html file) -
You can achieve this by using the following code -
For Code.gs:
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
function getUsers() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var users = ss.getRange(1, 1, ss.getLastRow(), 1).getValues();
return users;
}
For Index.html file:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onSuccess1(users) {
var div = document.getElementById('userFirstNames');
div.innerHTML = users;
}
google.script.run.withSuccessHandler(onSuccess1).getUsers();
</script>
</head>
<body>
<div id='userFirstNames'></div>
</body>
</html>
Hope this helps.