Can I modify HtmlService Output using Google Apps Script? - google-apps-script

I made an web app which copies folder and files of google drive. My goal is to show the name of the file and folder which is being copied on the web page('messages' id). Is it possible to change the web page from the google apps script?
When I open the web app, the GAS shows an output page using a form.html, and when I push the copy button, it will start start() function and begin copying the folders. After the successful process, the successHandler will call onSuccess() and it will change the innerHTML to Success.
I'd like to change the innerHTML during the copy process according to the folders' and files' name, but I don't know how to change it from the GAS function start() or copyFolder().
Thank you.
code.gs
function doGet(){
return HtmlService.createHtmlOutputFromFile('form');
}
function start() {
var sourceFolderId = "FOLDERID";
var targetFolder = "TARGET_FOLDER_NAME";
var source = DriveApp.getFolderById(sourceFolderId);
var target = DriveApp.createFolder(targetFolder);
copyFolder(source, target);
}
function copyFolder(source, target) {
var folders = source.getFolders();
var files = source.getFiles();
while(files.hasNext()) {
var file = files.next();
file.makeCopy(file.getName(), target);
}
while(folders.hasNext()) {
var subFolder = folders.next();
var folderName = subFolder.getName();
var targetFolder = target.createFolder(folderName);
copyFolder(subFolder, targetFolder);
}
}
form.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function callFolderDownload() {
document.getElementById('messages').innerHTML = 'Copying...';
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.start();
}
function onSuccess() {
document.getElementById('messages').innerHTML = 'Success';
}
function onFailure(error)
{
document.getElementById('messages').innerHTML = error.message;
}
</script>
</head>
<body>
<div id="messages">Click the button to copy the folder.
</div>
<div>
<button type="button" onclick='callFolderDownload();' id="download">Copy</button>
</div>
</body>
</html>

You would need to break up your execution into various parts
You can't "stream" updates from the server process of copying a folder.
At the expense of making your execution slower, you can break it up into various phases, and at the end of each phase, update the client side with status. Remember that for every different stage you are having to:
- Make a call to Apps Script from client.
- Apps Script makes a separate call to Drive.
- Drive responds to Apps Script.
- Apps Script responds to client.
To get a message for each file copied, this needs to happen for each file. Depending on how many files you need to copy, this may not work. In that case, you would probably have to do it in batches. That is, maybe when you get the list of files back from the server, you can send 10 to the server to copy per request.
The key is that there has to be a separate call and response from server for each "real time update". These calls and responses need to be asynchronously programmed, that is, each stage needs to wait for previous stages to complete. This can result in the "pyramid of doom" which can be quite ugly and hard to understand:
google.script.run
.withSuccessHandler((targetId) => {
google.script.run
.withSuccessHandler((list) => {
newMessage("Starting to copy...");
list.forEach((item) => {
google.script.run
.withSuccessHandler(() => {
newMessage(item.name + " has been copied");
})
.copyItem(item.id, targetId);
});
})
.getFolderItems();
})
.createTargetFolder(targetName);
This can be made better with promises or the async await syntax, but in the interest of not making this answer too long, I have omitted it.
Below is an example of it working:
HTML
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
// This is a utility function to append a new message to the html
function newMessage(message) {
let messageElement = document.getElementById("messages")
let sourceDiv = document.createElement("div")
sourceDiv.innerText = message
messageElement.append(sourceDiv)
}
function copyFolder() {
newMessage("Preparing to copy...")
let targetName = document.getElementById("newFolderName").value
console.log(targetName)
google.script.run
.withSuccessHandler(targetId => {
google.script.run
.withSuccessHandler(list => {
newMessage("Starting to copy...")
list.forEach(item => {
google.script.run
.withSuccessHandler(() => {
newMessage(item.name + " has been copied")
})
.copyItem(item.id, targetId)
})
})
.getFolderItems()
})
.createTargetFolder(targetName)
}
</script>
</head>
<body>
<div>
<label for="newFolderName">Type in Name of new folder that contents will be copied to</label>
<input id="newFolderName" type="text">
<button type="button" onclick='copyFolder();' id="download">Copy</button>
</div>
<div id="messages">
-
</div>
</body>
</html>
Apps Script
class FileToCopy {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
function doGet() {
return HtmlService.createHtmlOutputFromFile('form');
}
function createTargetFolder(name){
Logger.log(name)
var target = DriveApp.createFolder(name);
return target.getId()
}
function getFolderItems() {
var sourceFolderId = "[SOURCE_FOLDER_ID]";
var source = DriveApp.getFolderById(sourceFolderId);
var files = source.getFiles();
let output = []
while (files.hasNext()) {
var file = files.next();
let id = file.getId();
let name = file.getName();
let newItem = new FileToCopy(id,name)
output.push(newItem)
}
return output;
}
function copyItem(id, target) {
let file = DriveApp.getFileById(id)
let folder = DriveApp.getFolderById(target)
file.makeCopy(folder);
}
Result
Reference
Client to Server Communications

Related

Upload file and past the link into active cell a spreadsheet

Trying to make a script to upload a file and paste the downloaded file's link into the active cell of a google spreadsheet.
After clicking "Upload" in the modal window, the file is not written to Google Drive and, accordingly, the link is not written to the cell
Code.gs
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('File')
.addItem('Attach...', 'showForm')
.addToUi();
}
function showForm() {
var html = HtmlService.createHtmlOutputFromFile('index');
SpreadsheetApp.getUi().showModalDialog(html, 'Upload File');
}
function uploadFile(e) {
var newFileName = e.fileName;
var blob = e.file;
var upFile = DriveApp.getFolderById('*FolderID*').createFile(blob).setName(newFileName);
Logger.log(upFile);
var fileUrl = upFile.getUrl();
var formula = '=HYPERLINK("' + fileUrl + '","' + newFileName + '")';
SpreadsheetApp.getActiveRange().setFormula( formula );
return "Uploaded!";
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_center">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
</head>
<body>
<form id="myForm" >
Select File: <input type="file" name="file" accept="*" /><br>
File name: <input type="text" name="fileName" /><br><br>
<input type="button" value="Upload" onclick="upload(this.parentNode);" />
</form>
<script>
window.onload=func1;
function func1() {
document.getElementById('myForm').addEventListener('submit', function(event) {
event.preventDefault();
});
}
function upload(obj){
google.script.run.withSuccessHandler(close).withFailureHandler(close).uploadFile(obj);
}
function close(e) {
console.log(e);
google.script.host.close();
}
</script>
</body>
</html>
Issue and workaround:
On December 9, 2021, the file object got to be able to be parsed from the Javascript side to the Google Apps Script side with V8 runtime. But, in this case, this can be used for only Web Apps. In the current stage, the sidebar and dialog cannot parse the file object on the Javascript side. Ref I think that this is the reason of your issue. So, in the current stage, it is required to send the file object as the string and the byte array for the sidebar and the dialog.
In this case, in order to achieve your goal using the current workaround, I would like to propose the following 2 patterns.
By the way, I think that in your Google Apps Script, an error occurs at SpreadsheetApp.getActiveCell().setFormula( formula );. Because SpreadsheetApp has no method of getActiveCell(). In this case, I think that getActiveRange() might be suitable.
Modified script:
In this modification, the file object is converted to the byte array, and the data is sent to Google Apps Script side.
Google Apps Script side:
function showForm() {
var html = HtmlService.createHtmlOutputFromFile('index');
SpreadsheetApp.getUi().showModalDialog(html, 'Upload File');
}
function uploadFile(e) {
var blob = Utilities.newBlob(...e);
var upFile = DriveApp.getFolderById('*FolderID*').createFile(blob).setName(e[2]);
Logger.log(upFile);
var fileUrl = upFile.getUrl();
var formula = '=HYPERLINK("' + fileUrl + '","' + e[2] + '")';
SpreadsheetApp.getActiveRange().setFormula(formula);
return "Uploaded!";
}
HTML & Javascript side:
<!DOCTYPE html>
<html>
<head>
<base target="_center">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
</head>
<body>
<form id="myForm">
Select File: <input type="file" name="file" accept="*" /><br>
File name: <input type="text" name="fileName" /><br><br>
<input type="button" value="Upload" onclick="upload(this.parentNode);" />
</form>
<script>
window.onload = func1;
function func1() {
document.getElementById('myForm').addEventListener('submit', function(event) {
event.preventDefault();
});
}
function upload(obj) {
const file = obj.file.files[0];
// --- For your additional request, I modified below script.
const extension = function(e) {
const temp = e.split(".");
return temp.length == 1 ? "" : temp.pop();
}(file.name);
const filename = `${obj.fileName.value}.${extension}`;
// ---
const fr = new FileReader();
fr.onload = e => {
const obj = [[...new Int8Array(e.target.result)], file.type, filename];
google.script.run.withSuccessHandler(close).withFailureHandler(close).uploadFile(obj);
};
fr.readAsArrayBuffer(file);
}
function close(e) {
console.log(e);
google.script.host.close();
}
</script>
</body>
</html>
Note:
In your script, I thought that the modified script might be a bit complicated. So, I posted the modified script as an answer.
Reference:
Related thread.
How do I convert jpg or png image to Blob via a Google Apps Script Web App?

Reusable Google doc Picker in google scripts - Picker Callback

Docu References:
Drive file picker v3
G Suite Dialogs: File Open Dialog
SO References:
Access data in Google App Script from spread sheet modal html form
How do I handle the call back using multiple Google File Picker
What to achieve?
In a Google Sheets script, I would like to define a Files Picker that returns the data of picked up files, provided that thereon, from another part of the scripts, the caller can receive that data.
Problem:
The file picker is launched as an html Modal dialog. After searching for a while, the only solution to get the data from the script that launched the picker is from the html script code:
set the callaback of the picker to a specific function: picker.setCallback(my_callback)
or use google.script.run.my_callback (i.e. from button Done for instance)
... provided that my_callback function defined in your script gets the data.
The problem with the above is that you cannot use the same picker for multiple purposes, because:
my_callback is fixed in the html script
my_callback cannot know for what purpose the picker was initially called (i.e. should it get the content?, should it give the information to some unknown caller?).
Once it gets the data, my_callback does not know what to do with it... unless my_callback is tied to only 1 caller; which does not seem correct, as that would require to have multiple html definitions for the picker, once per each reason you may invoke it, so it can call back to the proper function.
Any ideas?
global variables in scripts get re-initialized and cannot use PropertiesService to store values other than String (so no way to store the final picker_callback through a global var).
google.script.run does not offer calls by giving the name of the server-side function as String (reference) (which discards having a function to generate the picker_dialog.html by changing the callback function).
Sample Code
code.gs
function ui() {
return SpreadsheetApp.getUi();
}
function onOpen() {
ui().createMenu('ecoPortal Tools')
.addItem('Read a file', 'itemReadFile')
.addItem('Edit a file', 'itemEditFile')
.addToUi();
}
function itemReadFile() {
pickFile(readFile)
}
function itemEditFile() {
pickFile(editFile)
}
function readFile(data) {
/* do some stuff */
}
function editFile(data) {
/* do some stuff */
}
picker.gs:
function pickFile(callback) {
var html = HtmlService.createHtmlOutputFromFile('picker_dialog.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
// concept (discarded):
callback_set('picker', callback)
ui().showModalDialog(html, 'Select a file');
}
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
// picker callback hub
function pickerCallback(data) {
var callback = callback_get('picker');
callback_set('picker', null);
if (callback) callback.call(data);
}
picker_dialog.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
var DEVELOPER_KEY = '___PICKER_API_KEY_____';
var DIALOG_DIMENSIONS = {width: 600, height: 425};
var pickerApiLoaded = false;
// currently selected files data
var files_data = null;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
function getOAuthToken() {
console.log("going to call get auth token :)");
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
function createPicker(token) {
console.log("pickerApiLoadded", pickerApiLoaded);
console.log("token", token);
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
.addView(google.picker.ViewId.DOCS)
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
files_data = data;
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: <a href="' + url + '">' + title +
'</a><br>ID: ' + id;
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
function closeIt() {
google.script.host.close();
}
function returnSelectedFilesData() {
google.script.run.withSuccessHandler(closeIt).pickerCallback(files_data);
}
</script>
</head>
<body>
<div>
<button onclick='getOAuthToken()'>Select a file</button>
<p id='result'></p>
<button onclick='returnSelectedFilesData()'>Done</button>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
picker.setCallback(my_callback)
Picker callback is different from:
or use google.script.run.my_callback
The former calls a function on the frontend html while the latter calls a function in the server.
my_callback cannot know for what purpose the picker was initially called
You can send a argument to the server:
google.script.run.my_callback("readFile");
On the server side(code.gs),
fuction my_callback(command){
if(command === "readFile") Logger.log("Picker called me to readFile");
}
google.script.run does not offer calls by giving the name of the server-side function as String
Not true. Dot is used to access members of a object. You can use bracket notation to access a member as a string:
google.script.run["my_callback"]();
EDITED BY Q.ASKER:
In your case, to pass the files_data to the server side:
google.script.run.withSuccessHandler(closeIt)[my_callback](files_data);
Now, for my_callback (String variable) to be set from server side, you need to push it using templates:
function pickFile(str_callback) {
var htmlTpl = HtmlService.createTemplateFromFile('picker_dialog.html');
// push variables
htmlTpl.str_callback = str_callback;
var htmlOut = htmlTpl.evaluate()
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
ui().showModalDialog(htmlOut, 'Select a file');
}
The two unique changes that you need to make to your picker_dialog.html:
add printing scriptlet to set my_callback (<?= ... ?>)
use the google.script.run as mentioned
var my_callback = <?= str_callback? str_callback : 'defaultPickerCallbackToServer' ?>;
/* ... omitted code ... */
function returnSelectedFilesData() {
google.script.run.withSuccessHandler(closeDialog)[my_callback](files_data);
}
Now, when you call pickFile to open the frontend picker, you are able to set a different server callback that will receive the data with the file(s) chosen by the user.

Direct to a new page after form submission

Currently I've developed a Google Scripts API which is used for people to upload files to a shared Google Drive folder. After the file are uploaded successfully, I want them to be taken to a separate "Thank you" page so it is clear their upload has worked. Currently I only have a message on the same page to this effect and I cannot figure out how to direct to a new page that I have created.
This is the additional bit I found from different questions to try direct to a new page however this is not working so far, as it remains on the same upload form page. I have included it at the bottom of my code.gs file. Any ideas on how to direct to a custom page that just says "thank you" or something similar would be great!
function doPost(e) {
var template = HtmlService.createTemplateFromFile('Thanks.html');
return template.evaluate();
}
The rest of my code is as follows:
Code.gs:
function doGet() {
return HtmlService.createHtmlOutputFromFile('form').setSandboxMode(
HtmlService.SandboxMode.IFRAME);
}
function createFolder(parentFolderId, folderName) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId);
var folders = parentFolder.getFoldersByName(folderName);
var folder;
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = parentFolder.createFolder(folderName);
}
return {
'folderId' : folder.getId()
}
} catch (e) {
return {
'error' : e.toString()
}
}
}
function uploadFile(base64Data, fileName, folderId) {
try {
var splitBase = base64Data.split(','), type = splitBase[0].split(';')[0]
.replace('data:', '');
var byteCharacters = Utilities.base64Decode(splitBase[1]);
var ss = Utilities.newBlob(byteCharacters, type);
ss.setName(fileName);
var folder = DriveApp.getFolderById(folderId);
var files = folder.getFilesByName(fileName);
var file;
while (files.hasNext()) {
// delete existing files with the same name.
file = files.next();
folder.removeFile(file);
}
file = folder.createFile(ss);
return {
'folderId' : folderId,
'fileName' : file.getName()
};
} catch (e) {
return {
'error' : e.toString()
};
}
}
function doPost(e) {
var template = HtmlService.createTemplateFromFile('Thanks.html');
return template.evaluate();
}
Form.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div align="center">
<p><img src="https://drive.google.com/uc?export=download&id=0B1jx5BFambfiWDk1N1hoQnR5MGNELWRIM0YwZGVZNzRXcWZR"
height="140" width="400" ></p>
<div>
<form id="uploaderForm">
<label for="uploaderForm"> <b> Welcome to the Tesco's animal welfare and soy reporting system. </b> </label>
<BR>
<BR>
<div style="max-width:500px; word-wrap:break-word;">
Please add your contact information below and attach a copy of your company's animal welfare standard before clicking submit. Wait for the browser to confirm your submission and you may then close this page.
<BR>
<BR>
Thank you very much for your submission.
</div>
<BR>
<BR>
<div>
<input type="text" name="applicantName" id="applicantName"
placeholder="Your Name">
</div>
<BR>
<div>
<input type="text" name="applicantEmail" id="applicantEmail"
placeholder="Your Company">
</div>
<BR>
<BR>
<div>
<input type="file" name="filesToUpload" id="filesToUpload" multiple>
<input type="button" value="Submit" onclick="uploadFiles()">
</div>
</form>
<br>
<br>
<br>
<br>
<br>
<br>
<div id="output"></div>
<script>
var rootFolderId = '1-aYYuTczQzJpLQM3mEgOkWsibTak7KE_';
var numUploads = {};
numUploads.done = 0;
numUploads.total = 0;
// Upload the files into a folder in drive
// This is set to send them all to one folder (specificed in the .gs file)
function uploadFiles() {
var allFiles = document.getElementById('filesToUpload').files;
var applicantName = document.getElementById('applicantName').value;
if (!applicantName) {
window.alert('Missing applicant name!');
}
var applicantEmail = document.getElementById('applicantEmail').value;
if (!applicantEmail) {
window.alert('Missing applicant email!');
}
var folderName = applicantEmail;
if (allFiles.length == 0) {
window.alert('No file selected!');
} else {
numUploads.total = allFiles.length;
google.script.run.withSuccessHandler(function(r) {
// send files after the folder is created...
for (var i = 0; i < allFiles.length; i++) {
// Send each file at a time
uploadFile(allFiles[i], r.folderId);
}
}).createFolder(rootFolderId, folderName);
}
}
function uploadFile(file, folderId) {
var reader = new FileReader();
reader.onload = function(e) {
var content = reader.result;
document.getElementById('output').innerHTML = 'uploading '
+ file.name + '...';
//window.alert('uploading ' + file.name + '...');
google.script.run.withSuccessHandler(onFileUploaded)
.uploadFile(content, file.name, folderId);
}
reader.readAsDataURL(file);
}
function onFileUploaded(r) {
numUploads.done++;
document.getElementById('output').innerHTML = 'uploaded '
+ r.fileName + ' (' + numUploads.done + '/'
+ numUploads.total + ' files).';
if (numUploads.done == numUploads.total) {
document.getElementById('output').innerHTML = 'All of the '
+ numUploads.total + ' files are uploaded';
numUploads.done = 0;
}
}
</script>
<label for="uploaderForm">
Powered by 3Keel
</label>
</body>
</html>
Thanks.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Thank you for submitting!
</body>
</html>
EDIT:
I have changed this function as recommended:
if (numUploads.done == numUploads.total) {
window.location = 'Thanks.html';
numUploads.done = 0;
Now it is redirecting to another page but I am faced with this error:
That’s an error.
The requested URL was not found on this server. That’s all we know.
If you are looking for the solution of your issue yet, how about this answer?
You want to open Thanks.html when the process at Form.html is finished.
Form.html and Thanks.html are put in a project.
If my understanding is correct, how about this workaround? I have ever experienced with your situation. At that time, I could resolve this issue by this workaround.
Modification points:
It is not required to use doPost() to access to Thanks.html. I think that you can achieve what you want using doGet().
I think that #Scott Craig's answer can be also used for this situation. In my workaround, the URL of window.location = 'Thanks.html'; is modified.
Uses the URL of deployed Web Apps. In your script, when users access to your form, they access to the URL of the deployed Web Apps. In this workaround, it is used by adding a query parameter.
Modified scripts:
Form.html
For the script added in your question as "EDIT", please modify as follows.
From:
window.location = 'Thanks.html';
To:
window.location = 'https://script.google.com/macros/s/#####/exec?toThanks=true';
https://script.google.com/macros/s/#####/exec is the URL of the the deployed Web Apps. Please add a query parameter like toThanks=true. This is a sample query parameter.
Code.gs
Please modify doGet() as follows.
From:
function doGet() {
return HtmlService.createHtmlOutputFromFile('form')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
To:
function doGet(e) {
if (e.parameter.toThanks) {
return HtmlService.createHtmlOutputFromFile('Thanks')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
} else {
return HtmlService.createHtmlOutputFromFile('form')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
}
Note:
When the script of the deployed Web Apps was modified, please redeploy it as new version. By this, the latest script is reflected to Web Apps.
The flow of this modified script is as follows.
When users access to the Form.html, because the query parameter of toThanks=true is not used, Form.html is returned.
When onFileUploaded() is run and if (numUploads.done == numUploads.total) {} is true, it opens the Web Apps URL with the query parameter of toThanks=true. By this, if (e.parameter.toThanks) {} of doGet() is true, and Thanks.html is returned and opened it.
In my environment, I could confirm that this modified script worked. But if this didn't work in your environment, I'm sorry. At that time, I would like to think of about the issue.
I might be misunderstanding your question, but from what I understand, instead of this line:
document.getElementById('output').innerHTML = 'All of the '
+ numUploads.total + ' files are uploaded';
You want to redirect to Thanks.html. If that's correct, just replace the above line with:
window.location = 'Thanks.html';

Automated download of file from Drive in Web App?

I'm trying to write a polling web app that checks Google Drive and automatically downloads files without user interaction.
Using ContentService I have managed to get things working when I place the code in the doGet function.
However this only works once and there does not appear to be a way to refresh or reload the page automatically on a timer event.
Using a SetTimeout on the client side javascript I can get a function on the server side to automatically trigger at certain intervals but then I am stuck with what to do with the output from ContentService.
The on Success call back will not accept the output from createTextOutput.
My solution does not not need to be deployed and I'm happy to execute from the editor if that expands my choices.
So once I have the output from createTextOutput on my server side what am I supposed to do with it to get it back to the client in order to cause the file download?
I have included the code if that helps.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
setTimeout(
function ()
{
document.getElementById('results').innerHTML = 'Event Timer';
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.fetchFromGoogleDrive();
}, 60000);
function onSuccess(sHTML)
{
document.getElementById('results').innerHTML = 'File Downloaded ' + sHTML;
}
function onFailure(error)
{
document.getElementById('results').innerHTML = error.message;
}
</script>
</head>
<body>
<div id="results">Waiting to DownLoad!</div>
id="Fetch">Fetch!</button>
</body>
</html>
function doGet() {
Logger.log('doGet');
return HtmlService.createHtmlOutputFromFile('form.html');
}
function fetchFromGoogleDrive() {
//Logger.Log('fetchFromGoogleDrive');
var fileslist = DriveApp.searchFiles("Title contains 'Expected File'");
if (fileslist.hasNext()) {
//Logger.Log('File found');
var afile = fileslist.next();
var aname = afile.getName();
var acontent = afile.getAs('text/plain').getDataAsString();
var output = ContentService.createTextOutput();
output.setMimeType(ContentService.MimeType.CSV);
output.setContent(acontent);
output.downloadAsFile(aname);
return afile.getDownloadUrl();
}
else
{
//Logger.Log('No File Found');
return 'Nothing to download';
}
//Logger.log('All files processed.');
}
EDIT: Different answer after clarification.
If this is intended to run automated as a webapp what I would do is return the getDownloadUrl and create a new iFrame using that that as the source.
Apps Script
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function getDownloadLink(){
//slice removes last parameter gd=true. This needs to be removed. slice is a hack you should do something better
return DriveApp.getFileById("0B_j9_-NbJQQDckwxMHBzeVVuMHc").getDownloadUrl().slice(0,-8);
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<p id="dlBox"></p>
</body>
<script>
function buildLink(res){
var dlBox = document.createElement("iframe");
dlBox.src = res;
document.getElementById("dlBox").appendChild(dlBox)
}
//automate this as you need
google.script.run
.withSuccessHandler(buildLink)
.getDownloadLink();
</script>
</html>

Send drive folder list to listbox HTML

I have a script that is pulling all the folders in my drive. I want to push this data into a listbox that has been created in HTML (UiApp is deprecated)
The code I have is (note that the sheet.appendRow line doesn't work and if you know why and feel like explaining that would be awesome!!)
function updateFoldersListbox(FoldersListBox) {
var rootFolder = DriveApp.getRootFolder();
var driveFolders = rootFolder.getFolders();
while (driveFolders.hasNext()) {
var addMonitoredFolder = driveFolders.next();
Logger.log(addMonitoredFolder);
//sheet.appendRow(addMonitoredFolder);
}
The output of the logs is:
[15-09-26 01:38:46:603 BST] Test Folder
[15-09-26 01:38:46:605 BST] Untitled folder
[15-09-26 01:38:46:606 BST] Notes
Now to push that to a listbox and add test folder, untitled folder and notes as an option:
<select name="sometext" size="5">
<option>option1</option>
</select>
How is that done?
Put the following code in a script file:
function onOpen() {
SpreadsheetApp.getUi().createMenu('Dialog').addItem('Open', 'openDialog').addToUi();
};
function openDialog() {
var html = HtmlService.createHtmlOutputFromFile('Index')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select a Google Drive Folder:');
};
function updateFoldersListbox() {
var rootFolder = DriveApp.getRootFolder();
var driveFolders = rootFolder.getFolders();
var listnames = new Array();
while (driveFolders.hasNext()) {
var addMonitoredFolder = driveFolders.next().getName();
listnames.push([addMonitoredFolder]);
//Logger.log(addMonitoredFolder);
//sheet.appendRow(addMonitoredFolder);
}
return listnames;
};
And put the following code in a HTML file 'Index.html':
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<select id="menu"> </select>
<script
src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
// The code in this function runs when the page is loaded.
$(function() {
google.script.run.withSuccessHandler(showThings)
.updateFoldersListbox();
google.script.run.withSuccessHandler(showMenu)
.updateFoldersListbox();
});
function showThings(things) {
var list = $('#things');
list.empty();
for (var i = 0; i < things.length; i++) {
list.append('<li>' + things[i] + '</li>');
}
}
function showMenu(menuItems) {
var list = $('#menu');
list.find('option').remove(); // remove existing contents
for (var i = 0; i < menuItems.length; i++) {
list.append('<option>' + menuItems[i] + '</option>');
}
}
</script>
</body>
</html>
Now, run the function 'openDialog' from the script editor.
Or refresh your spreadsheet and run the function from the custom menu 'Dialog' > 'Open'.