google 脚本中的可重用 Google doc Picker - Picker 回调

Reusable Google doc Picker in google scripts - Picker Callback

文档参考:

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

SO 参考文献:

  1. How do I handle the call back using multiple Google File Picker

实现什么?

Google 表格脚本 中,我想定义一个文件选取器,returns data 选取的文件,前提是,调用者可以从脚本的另一部分接收到 data.

问题:

文件选取器作为 html Modal 对话框启动。搜索了一段时间后,从启动 picker 的脚本 中获取 data 的唯一解决方案是来自 html 脚本代码:

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

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

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

  1. my_callbackhtml 脚本
  2. 已修复
  3. my_callback 不知道最初调用 picker 的目的是什么(即 它应该获取内容吗?它应该将信息提供给某个未知来电者?)。

一旦它得到 datamy_callback 不知道如何处理它...除非 my_callback 只绑定到 1 个调用者;这似乎不正确,因为这需要为 picker 定义多个 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>

picker.setCallback(my_callback)

选择器回调不同于:

or use google.script.run.my_callback

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

my_callback cannot know for what purpose the picker was initially called

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

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 does not offer calls by giving the name of the server-side function as String

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

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 (<?= ... ?>)
  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由用户。