【问题标题】:Reusable Google doc Picker in google scripts - Picker Callback谷歌脚本中可重用的谷歌文档选择器 - 选择器回调
【发布时间】:2019-12-16 00:28:29
【问题描述】:

文档参考文献

  1. Drive file picker v3
  2. G Suite 对话:File Open Dialog

SO 参考文献

  1. Access data in Google App Script from spread sheet modal html form
  2. How do I handle the call back using multiple Google File Picker

要达到什么目标?

Google 表格脚本中,我想定义一个文件选择器,它返回所拾取文件的 data,前提是调用者可以从脚本的另一部分接收那个data

问题:

文件选择器作为html 模态对话框启动。找了一阵子,从启动picker的脚本中获取data的唯一解决方案是从html脚本代码:

  1. 将选择器的callaback设置为特定功能:picker.setCallback(my_callback)
  2. 或使用google.script.run.my_callback(例如,来自按钮Done

...前提是脚本中定义的my_callback 函数获得data

上面的问题是你不能将同一个picker用于多种用途,因为:

  1. my_callbackhtml 脚本中固定
  2. my_callback 不知道最初调用 picker 的目的是什么(即它应该获取内容吗?它应该将信息提供给一些未知的调用者吗?)。

一旦它得到datamy_callback 不知道如何处理它......除非my_callback 只与1 个调用者绑定;这似乎不正确,因为这需要为picker 定义多个html,每个您可以调用它的原因一次,因此它可以回调到正确的函数。

有什么想法吗?

  • 脚本中的全局变量被重新初始化,并且不能使用PropertiesService 来存储String 以外的值(因此无法通过全局变量存储最终的picker_callback)。
  • google.script.run 不会通过将服务器端函数的名称指定为 String (reference) 来提供调用(它放弃了通过更改回调函数来生成 picker_dialog.html 的函数)。

示例代码

code.gs

function ui() {
  return SpreadsheetApp.getUi();
}

function onOpen() {
  ui().createMenu('ecoPortal Tools')
      .addItem('Read a file', 'itemReadFile')
      .addItem('Edit a file', 'itemEditFile')
      .addToUi();
}

function itemReadFile() {
  pickFile(readFile)
}

function itemEditFile() {
  pickFile(editFile)
}

function readFile(data) {
  /* do some stuff */
}

function editFile(data) {
  /* do some stuff */
}

picker.gs:

function pickFile(callback) {
  var html = HtmlService.createHtmlOutputFromFile('picker_dialog.html')
      .setWidth(600)
      .setHeight(425)
      .setSandboxMode(HtmlService.SandboxMode.IFRAME);

  // concept (discarded):
  callback_set('picker', callback)
  ui().showModalDialog(html, 'Select a file');
}

function getOAuthToken() {
  DriveApp.getRootFolder();
  return ScriptApp.getOAuthToken();
}

// picker callback hub
function pickerCallback(data) {
  var callback = callback_get('picker');
  callback_set('picker', null);
  if (callback) callback.call(data);
}

picker_dialog.html

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
  <script>
    var DEVELOPER_KEY = '___PICKER_API_KEY_____';
    var DIALOG_DIMENSIONS = {width: 600, height: 425};
    var pickerApiLoaded = false;
    // currently selected files data
    var files_data = null;

    /**
     * Loads the Google Picker API.
     */
    function onApiLoad() {
      gapi.load('picker', {'callback': function() {
        pickerApiLoaded = true;
      }});
     }

    function getOAuthToken() {
      console.log("going to call get auth token :)");
      google.script.run.withSuccessHandler(createPicker)
          .withFailureHandler(showError).getOAuthToken();
    }

    function createPicker(token) {
      console.log("pickerApiLoadded", pickerApiLoaded);
      console.log("token", token);

      if (pickerApiLoaded && token) {
        var picker = new google.picker.PickerBuilder()
            .addView(google.picker.ViewId.DOCS)
            .enableFeature(google.picker.Feature.NAV_HIDDEN)
            .hideTitleBar()
            .setOAuthToken(token)
            .setDeveloperKey(DEVELOPER_KEY)
            .setCallback(pickerCallback)
            .setOrigin(google.script.host.origin)
            .setSize(DIALOG_DIMENSIONS.width - 2,
                DIALOG_DIMENSIONS.height - 2)
            .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) {
        files_data = data;
        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.';
      }
    }

    function showError(message) {
      document.getElementById('result').innerHTML = 'Error: ' + message;
    }

    function closeIt() {
      google.script.host.close();
    }

    function returnSelectedFilesData() {
      google.script.run.withSuccessHandler(closeIt).pickerCallback(files_data);
    }

  </script>
</head>
<body>
  <div>
    <button onclick='getOAuthToken()'>Select a file</button>
    <p id='result'></p>
    <button onclick='returnSelectedFilesData()'>Done</button>
  </div>
  <script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>

【问题讨论】:

  • 你读过this吗?多用途是什么意思?你的一些假设是错误的。但目前还不清楚你想做什么。
  • 假设我想使用选取器对获得的文件做不同的事情:(a) 拾取文件以加载一些内容,(b) 拾取文件以读取一些定义并使用它们从应用程序中,(c) 选择一个文件来写入一些结果,(d) 选择一个文件来导入一些选项卡。使用您提到的示例,callback 在每种情况下都会有所不同,不是吗?
  • 仍然不确定你想要什么,但我会添加一个答案。
  • 你能给出一个完整的概述吗?还是流量?类似于:用户想要获取文件的元数据>用户单击菜单>主功能>主功能打开选择器>用户在选择器中从他的驱动器中选择一个文件>文件从 html 发送到 gs/server=>服务器读取元数据文件并将其返回到 html>html 显示从服务器返回的数据=>成功
  • @TheMaster 我认为核心问题是我没有看到框架的客户端-服务器二元性。我正在考虑在服务器-客户端部分之间创建一个连续的工作流,尽管实际上存在分离的客户端和服务器端。

标签: google-apps-script google-picker


【解决方案1】:

picker.setCallback(my_callback)

选择器回调不同于:

或使用 google.script.run.my_callback

前者调用前端html的函数,后者调用服务端的函数。

my_callback 无法知道选择器最初被调用的目的

您可以向服务器发送参数:

google.script.run.my_callback("readFile");

在服务器端(code.gs),

fuction my_callback(command){
  if(command === "readFile") Logger.log("Picker called me to readFile");
}

google.script.run 不通过将服务器端函数的名称作为字符串来提供调用

不正确。点用于访问对象的成员。您可以使用括号表示法将成员作为字符串访问:

google.script.run["my_callback"]();

由 Q.ASKER 编辑:

在您的情况下,将files_data 传递给服务器端:

google.script.run.withSuccessHandler(closeIt)[my_callback](files_data);

现在,要从服务器端设置my_callback(字符串变量),您需要使用templates 推送它:

function pickFile(str_callback) {
  var htmlTpl = HtmlService.createTemplateFromFile('picker_dialog.html');
  // push variables
  htmlTpl.str_callback = str_callback;

  var htmlOut = htmlTpl.evaluate()
      .setWidth(600)
      .setHeight(425)
      .setSandboxMode(HtmlService.SandboxMode.IFRAME);

  ui().showModalDialog(htmlOut, 'Select a file');
}

您需要对picker_dialog.html 进行两个独特的更改:

  1. 添加printing scriptlet 设置my_callback (&lt;?= ... ?&gt;)
  2. 如前所述使用google.script.run
var my_callback       = <?= str_callback? str_callback : 'defaultPickerCallbackToServer' ?>;

/* ... omitted code ... */

function returnSelectedFilesData() {
  google.script.run.withSuccessHandler(closeDialog)[my_callback](files_data);
}

现在,当您调用 pickFile 打开前端选择器时,您可以设置不同的服务器 callback 来接收带有用户选择的文件的 data

【讨论】:

  • 你的最后一行起了作用......谢谢:)所以只需要通过更改回调来生成picker_dialog.html,并且必须这样做......会尝试回来跨度>
  • @rellampec 你在找templated html吗?
  • 我看了一下...嗯,这可能是唯一的选择,我猜?这个想法是pickFile函数使用参数callback生成picker_dialog.html,所以前端的returnSelectedFilesData函数使用google.script.run["string_callback"]()并通过在服务器端调用适当的函数正确关闭拾取文件的循环。
  • 我会用templates 试试你的建议,因为它看起来很干净。我看到可以使用前端脚本var 从服务器传递数据:)
  • 我得到了它使用你使用templates的建议;将str_callback 推入pickFile 并在picker_dialog.htmlreturnSelectedFilesData 函数中使用它,如下所示:google.script.run.withSuccessHandler(closeIt)[str_callback](files_data);。这允许通过将回调字符串作为参数在服务器端使用pickFile 来实现任何目的:pickFile("getContent")pickFile("addToSelected")pickFile("appendText")
猜你喜欢
  • 1970-01-01
  • 2018-03-21
  • 2015-08-15
  • 2017-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多