Google apps script won't update app - google-apps-script

I am brand new to this and I know this is probably something simple but I just can't seem to get this working. I found this app script online that lets people upload files to my Google Drive but when I try to change anything in it and save it doesn't reflect in the app. I try to add CSS or update the text in Google Script editor then press Save then Run doGet function but nothing pops up to show me the page and the app doesn't change at all. It is deployed and authorized already.
server.gs:
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('form.html');
}
function uploadFiles(form) {
try {
var dropbox = "Student Files";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var blob = form.myFile;
var file = folder.createFile(blob);
file.setDescription("Uploaded by " + form.myName);
return "File uploaded successfully " + file.getUrl();
} catch (error) {
return error.toString();
}
}
form.html:
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<?!= include('style'); ?>
<form id="myForm">
<input type="text" name="myName" placeholder="Your name..">
<input type="file" name="myFile">
<input type="submit" value="Upload File"
onclick="this.value='Uploading..';
google.script.run.withSuccessHandler(fileUploaded)
.uploadFiles(this.parentNode);
return false;">
</form>
<div id="output"></div>
<script>
function fileUploaded(status) {
document.getElementById('myForm').style.display = 'none';
document.getElementById('output').innerHTML = status;
}
</script>

Thanks to #billy98111
Each time you deploy your web app it uses the same version. That's why it does not update.
Once you made changes, create new version then deploy it. Then when you deploy choose the the version you want to run

If you are using the URL with 'exec' on the end, that URL only serves the last version deployed, it won't show you the development version. Secondly, in order for any client side HTML, JavaScript, CSS to be displayed, you must refresh the browser tab. You don't need to refresh the browser tab to see results of any changes in .gs server side code. I have experienced situations where it seems like I needed to reload the browser, or reboot my computer to get something to work, but I can't verify that, and it could have just been a stupid mistake on my part. If it turns out to be none of those issues, let me know. I highly doubt it has anything to do with your code itself. If it's a shared file, and you are not the owner, there can be situations where the owner didn't authorize a new permission, so new code that needs a different permission that had not been authorized before won't run.

I believe you haven't published your app yet!
You need to go to Publish, Deploy as Web App.
There you'll have to generate a new version and them you can Deploy it, after that, you'll get a window showing the Deployed URL (the official URL) and a test URL (in order to test each save you make on your code):
After that if you need to publish new modifications officially, you have to go to File, manage Versions and create a new Version, after that you choose this new version on the Deploy menu again.

Related

Google sheets modal doesn't submit form but as a standalone webapp it's working

I have a custom UI function, that opens a modal dialog in my Google Sheets file. The modal is a simple HTML with a form, that allows the user to choose a file from local hard drive, to upload it into Google Drive. The HTML part for this form is as follows:
<form>
<input type="file" name="theFile" id="file-check" accept="image/*">
<input type="button" class="btn btn-info" value="Add photo" id="add-image">
</form>
And the code for sending the data is as follows:
$("#add-image").click(function(){
var val = $('#file-check').val();
if (val == ''){alert('Choose a file to upload');return;}
$('#add-image').attr("disabled", true);
$('#add-image').attr("value", "Adding... please wait.");
google.script.run.withSuccessHandler(refreshIt).withFailureHandler(show_error).uploadImage(this.parentNode);
});
But when I submit the form, the response from the server is this:
{theFile=null}
I've tried to set up this modal dialog as a standalone web-app and it's working normally...
The response from the server from the standalone webapp is as follows:
{theFile=FileUpload}
And the file is being uploaded without any problems!
Why is it working normally as a standalone webapp but sending the null as a modal dialog?
Issue and workaround:
Why is it working normally as a standalone webapp but sending the null as a modal dialog?
I thought that the reason for this issue is due to the current specification. After V8 runtime was released, there was a bug that when the file is sent from HTML form to Google Apps Script side using google.script.run, the file blob was the invalid data. But, on Nov 26, 2021, this bug could be removed. Ref But, when I tested this, it was found that this bug could be removed for Web Apps, and this bug cannot be removed for the dialog and sidebar. Ref This has already been reported to the issue tracker. I believe that this bug will be resolved in the future updated.
In the current stage, in order to upload a file with the dialog and sidebar, it is required to use a workaround. In this answer, I would like to propose a workaround. When your script is modified, it becomes as follows.
Modified script:
From:
$("#add-image").click(function(){
var val = $('#file-check').val();
if (val == ''){alert('Choose a file to upload');return;}
$('#add-image').attr("disabled", true);
$('#add-image').attr("value", "Adding... please wait.");
google.script.run.withSuccessHandler(refreshIt).withFailureHandler(show_error).uploadImage(this.parentNode);
});
To:
$("#add-image").click(function(){
var val = $('#file-check').val();
if (val == ''){alert('Choose a file to upload');return;}
$('#add-image').attr("disabled", true);
$('#add-image').attr("value", "Adding... please wait.");
const file = this.parentNode.theFile.files[0];
const fr = new FileReader();
fr.onload = function(e) {
google.script.run.withSuccessHandler(refreshIt).withFailureHandler(show_error).uploadImage([[...new Int8Array(e.target.result)], file.type, file.name]);
};
fr.readAsArrayBuffer(file);
});
In this case, the function uploadImage of Google Apps Script can be modified as follows. Unfortunately, I cannot see your current script of uploadImage. So please modify your script using the following sample.
function uploadImage(obj) {
const blob = Utilities.newBlob(...obj);
DriveApp.createFile(blob);
return "done"; // Please set your expected return value.
}
References:
HTML form file-input fields not in blob compatible format
[Fixed] Google Apps Script Web App HTML form file-input fields not in blob compatible format

GetURL returns wrong URL

I have a Google App Script program that has a number of HTML pages. To generate the URL's for links to individual pages, I use this function on the server code:
function getScriptUrl() {
var url = ScriptApp.getService().getUrl();
return url;
}
to return a URL that I could then embed like this, on the client side:
<?var myURL = getScriptUrl();?><a class="btn btn-success" href='<?=myURL?>?page=CreateNew'>My Button/a>
This always returned the URL of the app. So, if am testing in the Dev version, it returns the Dev URL, if I am in the Exe version, it returns the Exe URL. But, now, if I am in the Dev version, it returns the Exe URL. This was never an issue in the past but started today. Does anyone know why this is happening or a better way to generate the URL to create links between pages?
EDIT:
I have tried to republish the app (in Legacy Editor and the New Editor), log out of G Suite account, clear my cache but these attempts did not work to address the issue.
This should a Google part issue, I use the same logic to auto direct to prod and test page. It works fine in the passed few month. But it can't work from last week.
I too have this problem. Until recently I had this code to help me distinguish whether I was testing the development version or the deployed version. It worked for months.
const url = ScriptApp.getService().getUrl();
if (url.endsWith('dev')) {
// more code here
}
Now getUrl() will always return the exec url.
(I know this isn't an answer, but I am a first time poster and do not have enough points to "Add a comment" and I don't want to ask a duplicate question)

Web App using Google Apps Script not working with distributed URL

What the program does: I have written a simple Google Apps Script that retrieves unread emails from a label in the Gmail account currently logged in, and returns the number of those emails, and displays it on the website.
Problem: This program works when I created a web app from the computer I developed it on, and run it from the website. However, if I copy the URL and distribute it to another computer that is logged in as another Google account, it doesn't work.
Code:
/*Sample.gs*/
function doGet(e){
return HtmlService.createHtmlOutputFromFile('Run.html');
}
function GetUnreadCount(sourceLabel){
return GmailApp.getUserLabelByName(sourceLabel).getUnreadCount();
}
function Main()
{
/*For testing, return unread email count from "CustomerA" label of Gmail*/
return GetUnreadCount("CustomerA");
}
.
/*Run.html*/
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function CallGetUnreadCount() {
document.getElementById('Messages').innerHTML = 'Counting...';
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.Main();
}
function onSuccess(UnreadCount)
{
document.getElementById('Messages').innerHTML = "There are " + UnreadCount + " unread emails.";
}
function onFailure(error)
{
document.getElementById('Messages').innerHTML = "Err: " + error.message;
}
</script>
</head>
<body>
<div>
<h1 id="Messages">Count # of unread emails</h1>
</div>
<div>
<button type="button" onclick='CallGetUnreadCount();' id="CountButton">Count Unread Emails</button>
</div>
</body>
</html>
This program works fine if I run it on the computer I wrote this script on. However, if I deploy it as a web app, and distribute the URL (ending with /exec), and run it on another computer that has logged in as a different Gmail account, it always returns 0 even when there are unread emails in CustomerA label.
What confuses me is, this same program works on another computer if I create a new project on that computer and copy-paste the above code into a .gs file and an html file, and deploy as a web app.
Why does the same program work if each computer deploys the web app on their own, but not work if I distribute the URL?
When Deploying as web app (Publish > Deploy as web app), there are a few options to configure:
Execute the app as: Can be either Me, or User accessing the web app. For your case, you must use the later to make it work as you expect.
Who has access to this app: Can be either Only myselft, Anyone within your domain or Anyone. You have to make sure it's either the 2nd or 3rd option, depending on your needs, so that the user can properly execute it.

AppScript: How to create an upload form?

I am developing an Add-on for Gmail using AppScript.
My objective is to create something similar to the image below. Any hints?
Problem
File upload in Gmail Add-ons. In short - not exactly. Gmail Add-ons use CardService class to build the Ui - and it doesn't have a file input type, nor any drag-and-drop functionality. But there is a workaround.
Step 1. Create trigger widget
Then, ensure that your Card contains a CardSection with an ImageButton, TextButton or KeyValue widget (KeyValue is deprecated, use DecoratedText) that has an OpenLink action set on them. When using the setUrl(url) method to setup URL to open on widget click, use the current project's URL (when deploying both as WebApp and Add-on) that can be accessed dynamically via ScriptApp.getService().getUrl() call.
Step 2. Create file submit form
In the Add-on project, create an Html file that will handle the file upload. You can use sample one or create your own implementation. the sample file uses FileReader Web API to handle the submitted file (note that client-to-server communication in Google Apps Script requires preventing submit event handler and calling a server-side function via goolge.script.run API only).
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<form>
<input name="file" type="file" />
<button id="submit" type="submit">Save file</button>
</form>
<script>
var form = document.forms[0];
form.addEventListener('submit', (event) => {
event.preventDefault();
var file = form.elements[0].files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = () => {
var buffer = reader.result;
var data = Array.from(new Int8Array(buffer));
google.script.run.withSuccessHandler((server) => {
top.window.close();
}).saveFile(data,file.name,file.type);
};
});
</script>
</body>
</html>
Step 3. Setup doGet()
In your WebApp code, add the required doGet() function that will show the file upload form that we created during step 2. It can be as simple as a couple lines of code (just make sure to return the html file parsed by HtmlService):
function doGet() {
var html = HtmlService.createHtmlOutputFromFile('file name from step 2');
return html;
}
Step 4. Handle file upload
In your WebApp code, add handler that will receive file data (this sample assumes you read it as byte[], see step 2 for details).
function saveFile(upload,name,mime) {
var blob = Utilities.newBlob(upload,mime,name);
var file = DriveApp.createFile(blob);
Logger.log(file.getUrl()) //test upload;
//handle file as needed;
return;
}
Step 5. Deploy as WebApp
Lastly, you will have to deploy your Add-on as both WebApp (or bundle with one) and an Add-on. Assuming you've already configured manifest for the Add-on, go to "Publish" menu, select "Deploy as web app", create a deployment and allow access to anyone.
Notes
This method won't allow you to easily update the Ui to show which files were uploaded, but you can add a withSuccessHandler() call to google.script.run that on successful server-side handling of the uploaded file closes the window with the form, save state info to cache / user properties. Then, if you set the OpenLink's OnClose property to RELOAD_ADD_ON (see step 1), you will be able to conditionally update Ui to notify the user of successful upload.
UPDATE: after Tanaike's comment I reworked the upload process to better handle files: changed binary string file read to ArrayBuffer transformed to Int8Array and uploaded as an Array instance.
Current issue is the .g* files upload (despite correct transfer). Will update the answer when solved.
References
OpenLink class reference;
FileReader Web API reference on MDN;
newBlob() method reference (Utilities class);
Client-to-server communication in GAS guide;
Creating and serving HTML in GAS guide;
Web Apps guide;

Chrome extension post-install hook/API function: does it exist?

Is there a Chrome extension post install hook/API function that will let me perform an action after the plugin is installed or updated?
I would like to perform an action after my extension is installed, and only right after it is installed. This action should only be performed once (post-install or post-update) of the extension.
Update
Some people have proposed setting the version of the extension in localStorage, the problem that I have is that the content script that has access to localStorage is not loaded into the page when the plugin is first installed.
AFAIK after a plugin is installed, and it makes use of a content script injected into the tab/page, the page has to be reloaded.
I don't know how to access localStorage from the background page; localStorage can only be accessed from a content script.
To get the version number from the background page to the content script requires the use of chrome API function to execute scripts:
chrome.tabs.executeScript(null, {code:function_to_execute}, function() { // callback });
However, when you install a plugin, and the page that this plugin needs to inject a content script into is already loaded, it does not inject the content script, you have to reload the page.
update 2
Looking at some of the tips provided in more detail, for the purpose of saving the version number, it is possible to access the localStorage of the background page. However, for what I need to do, which is reload a specific tab at a specific URL (in order to make sure the content script is the newest version) after installing or updating a plugin, it ended up being unnecessary to bother with localStorage.
For the sake of staying on topic, the advice given about writing the version number to localStorage (in the background page) and then checking against the version number in the manifest file is good enough to allow someone to run a script the first time it is installed/or updated.
HowTo
Make manifest file available to the background page (note: this is taken from somewhere else, I don't take credit for it, but I can't remember the source, if you know, let me know and I will add it).
// MAKE MANIFEST FILE AVAILABLE
chrome.manifest = (function() {
var manifestObject = false;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
manifestObject = JSON.parse(xhr.responseText);
}
};
xhr.open("GET", chrome.extension.getURL('/manifest.json'), false);
try {
xhr.send();
} catch(e) {
console.log('Couldn\'t load manifest.json');
}
return manifestObject;
})();
Now you can access your version number like this: chrome.manifest.version
To write to localStorage just pass it in like so: localStorage['my_plugin_version'] = chrome.manifest.version
You can do this using a background page. When the extension is installed, the background page is opened in the background, and thus executed. To make sure it's not executed every time, simply store a value in localStorage.