问题
I followed this tutorial: https://developers.google.com/apps-script/guides/dialogs#file-open_dialogs and I can open a Google Picker after clicking a button inside my addon sidebar.
I want to do 2 things for better UX for my users:
The first thing is when the Google Picker is open, I want to show a loading spinner inside my addon sidebar (to prevent users from interacting with the sidebar), so that users can only interact with the Google Picker until it's closed
After users choose a document or directly close the Google Picker, the loading spinner in the addon sidebar will also be disappeared immediately (we shouldn't reload the sidebar), and users can interact with the addon sidebar normally
Please note: the loading indicator is inside the addon sidebar (not inside the picker dialog)
What I have done is:
I have 2 files:
- sidebar.html
- picker.html
Both these html files contain html, css, js that you need to run each of them.
Inside the sidebar.html
, when I need to open the Picker, I will call:
function openPicker() {
// Show spinner here
showSpinner()
google.script.run
.withSuccessHandler(function() { console.log('ok') })
.showPicker()
}
Above function will call this showPicker() function in Code.gs
:
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('picker.html')
.setWidth(800)
.setHeight(600)
FormApp.getUi().showModalDialog(html, 'Select a file')
}
Inside the picker.html
, I have a callback:
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) {
google.script.host.close()
}
// This is a good place to hide the spinner,
// but this is picker.html, so it can not control code in sidebar.html to hide the spinner
}
The problem here is, the sidebar.html
can only know when to show the spinner, not when to close it.
The logic to detect when we should close the spinner lies in the picker.html itself (only the picker knows when the user has selected a document or canceled the picker).
And I don't know how to pass that logic to the sidebar.html
so that it can run hideSpinner()
somewhere inside sidebar.html
.
回答1:
Issue:
Sidebar and modal dialog are not able to communicate despite having same origin.
Solution:
It is possible to get a reference to the sidebar html from modal dialog through window.top
. From there, it is possible to
- directly communicate with each other
- use
window.postMessage()
to communicate with each other - use cookies/localstorage to communicate with each other
Without a reference to each other, it is still possible to communicate with each other through
- the server and script properties service. However, Here, one of them needs to poll the server at set intervals to get any updates from the other.
Sample script(using direct access):
addOn.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Addon</title>
<style>
#spinner {
display: none;
background-color: tomato;
position: absolute;
top: 1%;
width: 100%;
justify-items: center;
}
</style>
</head>
<body>
<div id="spinner"><p>Loading modal dialog...</p></div>
<div id="output"></div>
<script charset="utf-8">
google.script.run.withSuccessHandler(spinner).testModal();
function spinner(e) {
document.getElementById('spinner').style.display = e || 'flex';
}
(async () => {
//After modal dialog has finished, receiver will be resolved
let receiver = new Promise((res, rej) => {
window.modalDone = res;
});
var message = await receiver;
document.querySelector('#output').innerHTML = message;
//Do what you want here
})();
</script>
</body>
</html>
modalAddOn.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
</head>
<body>
Modal Dialog
<script>
(function findSideBar(limit) {
let f = window.top.frames;
for (let i = 0; i < limit; ++i) {
try {
if (
f[i] /*/iframedAppPanel*/ &&
f[i].length &&
f[i][0] && //#sandboxFrame
f[i][0][0] && //#userHtmlFrame
window !== f[i][0][0] //!== self
) {
console.info('Sidebar found ');
alert('Removing loadbar and closing self');
var sidebar = f[i][0][0];
sidebar.spinner('none'); //Remove sidebar spinner
sidebar.modalDone('Modal says Hi'); //Modal has finished
google.script.host.close();
}
} catch (e) {
console.error(e);
continue;
}
}
})(10);
</script>
</body>
</html>
code.gs
function testModal() {
SpreadsheetApp.getUi().showModelessDialog(
HtmlService.createHtmlOutputFromFile('modalAddOn')
.setHeight(500)
.setWidth(300),
' '
);
}
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Sidebar')
.addItem('Show Add-On', 'showSidebar')
.addToUi();
}
function showSidebar() {
SpreadsheetApp.getUi().showSidebar(
HtmlService.createTemplateFromFile('addOn.html').evaluate()
);
}
To Read:
- Window#frames
- Window#postMessage
- Same origin policy
- Document#cookie
- Window#storage
- Promises
- PropertiesService
Related questions:
- Window#postMessage
- Window#storage
- Origin
来源:https://stackoverflow.com/questions/58179202/how-to-show-hide-loading-spinner-in-addon-sidebar-while-google-picker-dialog-is