How to show/hide loading spinner in addon sidebar while Google Picker dialog is opened/closed?

北城余情 提交于 2020-01-06 06:59:55

问题


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:

  1. sidebar.html
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!