Update the card when a Dialog is closed - google-apps-script

I want to known is there any way to update the card when I close the dialog
//this function is called when click a button in the card
function showDialog() {
const html = HtmlService.createHtmlOutputFromFile('dialog.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select the spreadsheets');
}
//this function is called by client code
function callback(text) {
var card = CardService.newCardBuilder().build();
var navigation = CardService.newNavigation().updateCard(card);
return CardService.newActionResponseBuilder()
.setNavigation(navigation)
.setNotification('Import Succeed')
.build();
}
dialog.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
function onApiLoad() {
gapi.load('picker', {'callback': function() {
......
}});
}
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
.addView(new google.picker.DocsView(google.picker.ViewId.SPREADSHEETS)
.setMode(google.picker.DocsViewMode.LIST)
.setCallback(pickerCallback)
.........
.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) {
google.script.run.callback('');
google.script.host.close();
} else if (action == google.picker.Action.CANCEL) {
google.script.host.close();
}
}
</script>
</head>
<body>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
I can write the text in the current spreadsheet developer metadata, and check the developer metadata in a dead loop in the card like the below code, but if user didn't close the dialog in time, google will throw timeout error in the card
//this function is called when click a button in the card
function showDialog() {
const html = HtmlService.createHtmlOutputFromFile('dialog.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select the spreadsheets');
//But I must return a response within a certain time since google has time limit for response
var metadataList = SpreadsheetApp.getActive().getDeveloperMetadata();
//find the text in the metadataList, and return the response
}
//this function is called by client code
function callback(text) {
//updat the card with text
SpreadsheetApp.getActive().addDeveloperMetadata('text', text);
}

Tl;Dr There is no way to update a card when a dialog is closed.
While an Editor add-on and Workspace add-on might share the same Google Apps Script project / Google Cloud project they are still two different add-ons. At this time there is no way to directly communicate two add-ons.
Workspace add-ons don't have triggers for file edit / changes, only for user clicking a card button.
For your current approach, I think that the only thing that you can do is to provide instructions to the user to reopen the card.
Related
Update Google Calendar UI after changing visability setting via Workspace Add-On
How to fully refresh Google Addon Card (Google Sheets) which includes a drop down menu populated using sheet data when data changes?
Is it possible to refresh the Google Workspace Add-on sidebar card from installable trigger function?

Related

Google Docs Apps Script Loading Indicator

I'm running a script when I open/refresh a Google Doc. It takes some time to run and can affect values in the doc, so I want to show some type of loading indicator to the user until it's done. I would prefer to not overlay a modal, so the doc is still accessible, but apart from that any solution, even a hacky one, would be ok.
// EXECUTE SCRIPT ON OPEN
function onOpen() {
// SHOW LOADING TO USER
initializeDoc();
doSomeStuffThatTakesSomeTime();
const ui = DocumentApp.getUi();
ui.createMenu('MyMenu')
.addItem('Click', 'doSomeStuffThatTakesSomeTime')
.addToUi();
// TURN LOADING OFF
}
Loading...
function loading() {
const ui = DocumentApp.getUi();
ui.showModelessDialog(HtmlService.createHtmlOutput("Loading...."),"Please Wait");
Utilities.sleep(5000);
const hl = '<!DOCTYPE html><html><head><base target="_top"></head><script>window.onload=()=>{google.script.host.close();}</script><body></body></html>';
ui.showModelessDialog(HtmlService.createHtmlOutput(hl),"Good Bye")
}
You will require an installable onOpen() such as below:
function onMyOpen(e) {
loading();
}

Make a google Script Run from a HTML front

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.

Standalone Script - Google Drive Picker - "is not a function"

We are using google sheets for expense claims.
At the end of every month each employee fills out their expense claim and attaches their receipts on google drive through the sheet and then submits the data via email.
This has been already implemented and works well.
However we currently have a copy of the script attached to each sheet that we share with the employees so if there is an update to the code we have to do the update on every single document which is time consuming and error prone.
I would like to use one standalone script that is accessed from all of the google sheets shared with individuals.
I am having trouble with getting the drive picker working in the Standalone google script.
here is my picker.html file
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
// IMPORTANT: Replace the value for DEVELOPER_KEY with the API key obtained
// from the Google Developers Console.
var DEVELOPER_KEY = 'purposely removed';
var DIALOG_DIMENSIONS = {width: 600, height: 425};
var pickerApiLoaded = false;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
/**
* Gets the user's OAuth 2.0 access token from the server-side script so that
* it can be passed to Picker. This technique keeps Picker from needing to
* show its own authorization dialog, but is only possible if the OAuth scope
* that Picker needs is available in Apps Script. Otherwise, your Picker code
* will need to declare its own OAuth scopes.
*/
function getOAuthToken() {
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
/**
* Creates a Picker that can access the user's spreadsheets. This function
* uses advanced options to hide the Picker's left navigation panel and
* default title bar.
*
* #param {string} token An OAuth 2.0 access token that lets Picker access the
* file type specified in the addView call.
*/
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
// Instruct Picker to display only spreadsheets in Drive. For other
// views, see https://developers.google.com/picker/docs/#otherviews
.addView(google.picker.ViewId.SPREADSHEETS)
// Hide the navigation panel so that Picker fills more of the dialog.
.enableFeature(google.picker.Feature.NAV_HIDDEN)
// Hide the title bar since an Apps Script dialog already has a title.
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
// Instruct Picker to fill the dialog, minus 2 pixels for the border.
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* #param {object} data The response object.
*/
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
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.';
}
}
/**
* Displays an error message within the #result element.
*
* #param {string} message The error message to display.
*/
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body>
<div>
<button onclick='getOAuthToken()'>Select a file</button>
<p id='result'></p>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
I then get this:
Google Drive Picker
but when I click the Select a file button i get this:
Uncaught TypeError: google.script.run.withSuccessHandler(...).withFailureHandler(...).getOAuthToken is not a function
at getOAuthToken (userCodeAppPanel:20)
at HTMLButtonElement.onclick (userCodeAppPanel:1)
my Code.gs contains this function along with functions to process the selected files.
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
Let me know if you need further information.
I already tried this solution and it didn't work:
Cannot call Google Script API Functions from Web App (TypeError: Cannot read property 'run' of undefined)
Any help is much appreciated.
Thank you
How to reproduce the problem:
go to: https://script.google.com/
Create a new script.
In the Code.gs file put this:
function saonopen() {
var ui = SpreadsheetApp.getUi();
// add some functions to the UI
ui.createMenu('Menu')
.addItem('Add Attachments', 'tsc.showPicker')
.addToUi();
}
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('picker.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select a file');
}
function processFilesChosen(ids, titles, urls) {
var targetSheetCost = switchTargetMonth('sheet').cost;
// record the files on the target cost sheet
for (var i in titles) {
i = parseInt(i)
targetSheetCost.getRange("H"+(i+3)).setFormula('=HYPERLINK("'+urls[i]+'","'+titles[i]+'")');
targetSheetCost.getRange("I"+(i+3)).setValue(ids[i]);
// Because of Google security update, we can not use this setSharing.
// DriveApp.getFileById(ids[i]).setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.COMMENT);
}
}
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
Add a new html file, call it picker.html and put the code posted earlier under picker.html.
Publish the script as a API Executable and note the API ID
create a new google sheet and import the standalone script as a library from resources menu on the script view using the API ID noted earlier. It will as for an identifier - put:
tsc
In the Code.gs file put:
function onOpen() {
tsc.saonopen()
}
Finally enable Picker and Drive API Access for both projects.
Drive & Picker API Access

Google app script return undefined before user gives answer yes/no on dialog

The method return is undefined before user clicks yes/no. According to Google app script, server process should be frozen when dialog shows up. But in my test, the value is returned before users clicks yes/no.
function getValues() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var okToInsert = true;
if (sheet.getName() !== "xxx") {
var ui = SpreadsheetApp.getUi();
var alert = ui.alert('Confirm',
'Your active sheet is not "xxx". Continue to insert?',
ui.ButtonSet.YES_NO);
if(alert == ui.Button.NO){
okToInsert = false;
}
}
return {
"okToInsert": okToInsert
};
}
This is my frontend js:
google.script.run
.withSuccessHandler(
function(selectedRange, scope) {
console.log(">>> selectedRange: " + selectedRange);
})
.withFailureHandler(
function(msg, scope) {
})
.withUserObject($scope)
.getValues();
This is how I enable the sidebar:
var ui = HtmlService.createTemplateFromFile('sidebar').evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setTitle('this is title');
SpreadsheetApp.getUi().showSidebar(ui);
I have a sample sidebar add-on called "Dialog return test" created here.
https://docs.google.com/spreadsheets/d/1eHwjHKuBIDw2WOTRU5CZAu9m9V-9mXimFyMlGXbPl-U/edit?usp=sharing
Your code runs perfectly for me:
I get the dialogue prompt and return is defined with no errors. It's likely the issue is with another part of your code, or possibly the script file itself. I would recommend trying it on a new script in a new sheets file and try and narrow the issue down from there.

Google App Script - Display PDF on Form Submit

I have a current Google Form and App Script where the Apps Script is taking the form data, putting it into a spreadsheet template (which does some calculations) then e-mails that spreadsheet as a PDF.
Is there a way in Google Apps Script to have this PDF displayed in the web browser rather than being sent in an e-mail?
You can use this workround .
pag.html
<html>
<input type="submit" onclick="downloadPDF()"/>
Click here to open
<script>
function sucess(e) {
alert(e);
var downloadLink = document.getElementById("myDownloadLink");
downloadLink.href=e;
downloadLink.style.display = 'block';
}
function downloadPDF() {
google.script.run.withFailureHandler(alert).withSuccessHandler(sucess).getFile();
}
</script>
</html>
code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile("pag");
}
function getFile() {
return DriveApp.getFileById("1pczv0kRvgpI87owXWUXTtm4wXXsiG8-ErVWFtv05izI").getUrl();
}