【问题标题】:How could I communicate a page(modal dialog) with its sibling (sidebar)?如何将页面(模式对话框)与其兄弟(侧边栏)通信?
【发布时间】:2021-12-03 08:10:01
【问题描述】:

我正在将我的脚本转换为附加组件。其中一个需求是配置模板,因此我编写了一个启动字段选择器的侧边栏。由于侧边栏没有足够的空间供选择器使用,我必须通过在服务器端调用以下代码从从侧边栏创建的模式对话框中启动它:

var html = HtmlService.createHtmlOutputFromFile('TemplatePicker.html')
  .setWidth(600).setHeight(425);
SpreadsheetApp.getUi().showModalDialog(html, 'Select the file with the template');

我的问题是,一旦用户选择了文件,当我获得所选文件的 id 时,我无法将该 id 传递给侧边栏。我尝试调用someJSFunctionOfSidebar(id)parent.someJSFunctionOfSidebar(id),但它不起作用,所以我最终结束了将值传递到服务器端并从那里重新加载侧边栏,但是它非常慢并且产生的效果很难看。

我的问题是:

有没有办法将客户端级别的值从使用SpreadsheetApp.getUi().showModalDialog 创建的模态对话框传递给其父级?也许它不是它的父级,这就是它不起作用的原因。

【问题讨论】:

  • 我不相信有办法做到这一点。我相信侧边栏和模式就像两个完全独立的应用程序,或者更像是两个独立的会话。选择文件选择器后,即使是 mailchimp 邮件合并插件也必须重新加载侧边栏。
  • 如果您由当前用户运行这两个元素,那么您可以使用Properties Service
  • @AlexanderIvanov 我知道,事实上我这样做是为了在他们之间共享数据,但这并不能解决在不重新加载侧边栏的情况下传达 UI 的部分。

标签: google-apps-script google-sheets web-applications google-docs google-picker


【解决方案1】:

也许它实际上不是它的父代,这就是它不起作用的原因。

是的——这里实际上并没有 DOM 父/子关系。侧边栏和模式对话框都是从服务器端脚本启动的,并且是独立的。不过,它们都可以与服务器通信,因此您可以使用存储转发技术将结果从选择器获取到侧边栏。

基本思路:

  • 侧边栏将在请求启动选择器后立即开始轮询服务器以获取选择器的结果。
  • 选择器的结果将使用google.script.run发送到服务器。
  • 服务器将临时存储结果 - 这可以像全局变量一样简单,具体取决于您的情况。
  • 一旦有结果,下一次投票将检索它。

查看How to poll a Google Doc from an add-on 了解轮询器的基本概念。

【讨论】:

  • 哇,这个很可能会成为热门 :-) 所以让我成为第一个投票的人吧!
  • 是的,鉴于限制,我认为这是要遵循的路径,谢谢!
  • @Mogsdad,我什至没有尝试,因为最简单的方法(重新加载侧边栏并通过用户属性进行通信)对我的客户来说已经足够了。
  • 也许它不是真正的父级,这就是它不起作用的原因。 没错,但他们都有相同的父级(他们是兄弟姐妹)并且是 @ 987654322@直接通过父窗口进行通信(window.top)。
  • @themaster 现在可能,而不是在 2014 年 - 当前用户会欢迎 2019 年的答案。
【解决方案2】:

问题:

侧边栏和模式对话框(兄弟)尽管来源相同,但无法通信。

解决办法:

可以通过父级父级window.top 从模态对话框中获取对侧边栏 html 的引用,即使父级是跨域的。从那里,有可能

  • 直接相互交流
  • 使用window.postMessage()相互交流

没有相互引用,仍然可以通过相互通信

  • 服务器和脚本属性服务。但是,在这里,其中一个需要以设定的时间间隔轮询服务器以从另一个获取任何更新(如 here 所示)。
  • 使用 cookie/localstorage 相互通信

阅读:

示例脚本(通过跨域框架直接访问window.top):

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()
  );
}

相关问题:

【讨论】:

  • 谢谢。只是一个问题:这条线做了什么:google.script.run.withSuccessHandler(spinner)?
  • @Hoang 我想在调用 testModal 后显示微调器。调用testModal>服务器脚本完成>调用微调器函数>显示隐藏微调器。
  • @Hoang 我不这么认为。 res 是一个函数,当调用它时,resolves 待处理的承诺(receiver)。 window.modalDone = res;var modalDone = res; 相同,除了变量 modalDone 在“全局范围”中声明:它与 modalDone=res; 相同。为什么我要在全局范围内声明一个值为res 的新变量,一个函数?这样就可以直接从模态对话框中调用它:sidebar.modalDone('Modal says Hi')... 与res('Modal says Hi') 相同——promise receiverModal says Hi 解析。希望对您有所帮助。
  • @ScrapeHeap /**/ 之间的任何内容都被视为评论并且不相关。还要注意语法颜色/突出显示,看看整个评论部分是如何在此处和气体编辑器中以浅色显示的。 f[i] 是必需的,因为如果寡妇中只有 2 个帧并且 i 是 3,那么 f[3] 应该评估为 undefined => undefinedfalsy。真假见 mdn
  • @BJG87 应该是可能的。只需命名 asyc 函数并从函数内部再次调用该函数 //Do what you want here
猜你喜欢
  • 1970-01-01
  • 2020-03-07
  • 2013-02-16
  • 1970-01-01
  • 2012-08-21
  • 2021-10-23
  • 1970-01-01
相关资源
最近更新 更多