如何向HTML传递参数?

16

我有一个脚本,使用了文件选择器,但我需要传递一个特定的参数,名为 userId,并将其作为全局变量保留在调用脚本中。由于调用是异步的,似乎无法访问此参数。是否有一种方法可以从html文件中访问该参数或将此参数传递给html?

我可能混淆了模板化的HTML非模板化的HTML

以下是调用代码(通过电子表格中的菜单项启动):

function syncStudentsFile(userId, ss) {
  scriptUser_(userId);  // save userId
  Logger.log('SRSConnect : syncStudentsFile : userId:'+userId);  // userId is correct here
  var ss = SpreadsheetApp.getActiveSpreadsheet();  
  var html = HtmlService.createHtmlOutputFromFile('PickerSync.html')
    .setWidth(600).setHeight(425);
  SpreadsheetApp.getUi().showModalDialog(html, 'Select a file');
}

function scriptUser_(userId) {
  if (userId !== undefined)
    sUserId = userId; // Global variable
  try { return sUserId; } catch (e) { return undefined; }
}

function getOAuthToken() {  // used by Picker
  DriveApp.getRootFolder();
  return ScriptApp.getOAuthToken();
}

这是HTML选择器文件:

<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">

<script type="text/javascript">
  var DEVELOPER_KEY = '..............';
  var DIALOG_DIMENSIONS = {width: 600, height: 425};
  var pickerApiLoaded = false;

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

  /**
   * Gets the user's access token from the server-side script so that
   * it can be passed to Picker. This technique keeps Picker from needing to
   * show its own authorization dialog, but is only possible if the OAuth scope
   * that Picker needs is available in Apps Script. Otherwise, your Picker code
   * will need to declare its own OAuth scopes.
   */
  function getOAuthToken() {
    google.script.run.withSuccessHandler(createPicker)
        .withFailureHandler(showError).getOAuthToken();
  }

  /**
   * Creates a Picker that can access the user's spreadsheets. This function
   * uses advanced options to hide the Picker's left navigation panel and
   * default title bar.
   *
   * @param {string} token An OAuth 2.0 access token that lets Picker access the
   *     file type specified in the addView call.
   */
  function createPicker(token) {
    if (pickerApiLoaded && token) {
      var uploadView = new google.picker.DocsUploadView();
      var picker = new google.picker.PickerBuilder()
          // Instruct Picker to display only spreadsheets in Drive. For other
          // views, see https://developers.google.com/picker/docs/#otherviews
          .addView(google.picker.ViewId.DOCS)
          .addView(google.picker.ViewId.RECENTLY_PICKED)
          .addView(uploadView)
          .hideTitleBar()
          .setOAuthToken(token)
          .setDeveloperKey(DEVELOPER_KEY)
          .setCallback(pickerCallback)
          // Instruct Picker to fill the dialog, minus 2 pixels for the border.
          .setSize(DIALOG_DIMENSIONS.width - 2,
              DIALOG_DIMENSIONS.height - 2)
          .build();
      picker.setVisible(true);
    } else {
      showError('Unable to load the file picker.');
    }
  }

  /**
   * A callback function that extracts the chosen document's metadata from the
   * response object. For details on the response object, see
   * https://developers.google.com/picker/docs/result
   *
   * @param {object} data The response object.
   */
  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];
      google.script.host.close();
      // --------------> user global parameter sUserId set earlier
      google.script.run.PickerSyncFile(sUserId, id);
    } else if (action == google.picker.Action.CANCEL) {
      google.script.host.close();
    }
  }

  /**
   * Displays an error message within the #result element.
   *
   * @param {string} message The error message to display.
   */
  function showError(message) {
    document.getElementById('result').innerHTML = 'Error: ' + message;
  }
</script>

<div>
  <script>getOAuthToken()</script>
  <p id='result'></p>
  <input type="button" value="Close" onclick="google.script.host.close()" />
</div>

以下是选择器代码:

function pickerSyncFile(userId, id) {
  Logger.log('userId:'+userId);  // BUG: it is null
  Logger.log('id:'+id);  // id returned well from picker

  // rest of code here but userId was is incorrect
}
5个回答

34
最安全的方式是将所需数据直接传递到HTML中。如果使用属性或缓存服务,可能会在多个同时用户下变得复杂或失败。
有许多技术可以将初始对象从服务器(.gs)传递到客户端(.html)。
使用HtmlTemplate,您可以执行如下操作:
//.gs 文件
function doGet() {
    var htmlTemplate = HtmlService.createTemplateFromFile('template-client');
    htmlTemplate.dataFromServerTemplate = { first: "hello", last: "world" };
    var htmlOutput = htmlTemplate.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME)
        .setTitle('sample');
    return htmlOutput;
}

并且在你的 template-client.html 文件中:

<!DOCTYPE html>

<script>
    var data = <?!= JSON.stringify(dataFromServerTemplate) ?>; //Stores the data directly in the javascript code
    // sample usage
    function initialize() {
        document.getElementById("myTitle").innerText = data.first + " - " + data.last;
        //or use jquery:  $("#myTitle").text(data.first + " - " + data.last);
    }
    // use onload or use jquery to call your initialization after the document loads
    window.onload = initialize;
</script>


<html>
<body>
    <H2 id="myTitle"></H2>
</body>
</html>

通过向HtmlOutput附加一个隐藏的div,也可以不使用模板来实现:

//.gs 文件:

function appendDataToHtmlOutput(data, htmlOutput, idData) {
    if (!idData)
        idData = "mydata_htmlservice";

    // data is encoded after stringifying to guarantee a safe string that will never conflict with the html.
    // downside: increases the storage size by about 30%. If that is a concern (when passing huge objects) you may use base94
    // or even base128 encoding but that requires more code and can have issues, see https://dev59.com/i2025IYBdhLWcg3wblc3
    var strAppend = "<div id='" + idData + "' style='display:none;'>" + Utilities.base64Encode(JSON.stringify(data)) + "</div>";
    return htmlOutput.append(strAppend);
}


// sample usage:
function doGet() {
    var htmlOutput = HtmlService.createHtmlOutputFromFile('html-sample')
        .setSandboxMode(HtmlService.SandboxMode.IFRAME)
        .setTitle('sample');

    // data can be any (serializable) javascript object.
    // if your data is a native value (like a single number) pass an object like {num:myNumber}
    var data = { first: "hello", last: "world" };
    // appendDataToHtmlOutput modifies the html and returns the same htmlOutput object
    return appendDataToHtmlOutput(data, htmlOutput);
}

并且在您的 output-client.html 文件中:

<!DOCTYPE html>
<script>
    /**
    * getDataFromHtml
    *
    * Inputs
    * idData: optional. id for the data element. defaults to "mydata_htmlservice"
    *
    * Returns
    * The stored data object
    */
    function getDataFromHtml(idData) {
        if (!idData)
            idData = "mydata_htmlservice";
        var dataEncoded = document.getElementById(idData).innerHTML;
        var data = JSON.parse(atob(dataEncoded));
        return data;
    }
    // sample usage of getDataFromHtml
    function initialize() {
        var data = getDataFromHtml();
        document.getElementById("myTitle").innerText = data.first + " - " + data.last;
        //or use jquery:  $("#myTitle").text(data.first + " - " + data.last);
    }
    // use onload or use jquery to call your initialization after the document loads
    window.onload = initialize;
</script>
<html>
<body>
    <H2 id="myTitle"></H2>
</body>
</html>


这里有一个我做的小github,详细比较和更好地解释了这两种方法:https://github.com/zmandel/htmlService-get-set-data


对于使用UTF-8字符的编码,请使用以下代码:Utilities.base64Encode(past, Utilities.Charset.UTF_8) - Sacru2red
一个很好的方法让我了解HTML脚本标签。谢谢! - PeterChen

6

我经常使用HtmlService模板将静态值推送到客户端。

index.html

<script>
 var domain = "<?=domain?>"; 
</script>

code.gs

var ui  = HtmlService.createTemplateFromFile('Sidebar');
ui.domain = domain;
return ui.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME).setTitle(strings[lang][domain]);

谢谢你的回复,但是我该如何将模板HTML与我的HTML代码结合使用呢?这样才能让它正常工作。 - theworldismyoyster
你的问题是如何将参数传递给HTML。你的代码显示你理解google.script.run,所以我向你展示了模板方法。请修改并澄清你的问题。 - Spencer Easton
我试图将这两个东西结合起来,但似乎没有成功,因此寻求传递参数的方法。在(syncStudentsFile)中对HTML进行评估一直返回错误。我对如何处理这个HTML并不是很专业,因此提出了我的问题。 - theworldismyoyster
这个解决方案是我认为官方的方式,但它有一个问题:当模板文件太大时,应用脚本会返回“超过最大堆栈深度”的错误。 - 0xh8h

3
在你的代码中:
function scriptUser_(userId) {
  if (userId !== undefined)
    sUserId = userId; // Global variable
  try { return sUserId; } catch (e) { return undefined; }
}

您正在给名为“sUserId”的全局变量赋值。但是,当您尝试检索它时,却什么也没有。全局变量在当前代码实例运行完成后立即失去其值。全局变量不会保留其值。
您需要使用 属性服务(Properties Service) 来存储该变量值。或者您可以使用缓存服务(Cache service)。如果您想让用户ID的值在一段时间后过期,请使用缓存服务。

缓存似乎没有起作用,但我会尝试使用Properties Service...如果可以传递参数的话,这将更容易。谢谢! - theworldismyoyster

1
通过追加到HTML文件中,如下所示。
在Code.gs中。
function showDialog() {
    var html = HtmlService.createHtmlOutputFromFile('html-sample')
                .setSandboxMode(HtmlService.SandboxMode.IFRAME)
                .setWidth(600)
                .setHeight(425);

    var data = "Hello World!";
    var strAppend = "<div id='id_for_div' style='display:none;'>" + data + "</div>";
    html.append(strAppend);

   var title = "Demo";
   SpreadsheetApp.getUi().showModalDialog(html, title); // or DocumentApp or SlidesApp or FormApp.
}

html-sample.html

<!DOCTYPE html>
<html>
   <head>
      <script>
         function setText(text) {
           var element = document.getElementById("myid");
           element.innerHTML = text;
         }

         function getDataFromHtml() {
           var id = "id_for_div";
           var dataEncoded = document.getElementById(id).innerHTML;
           setText(dataEncoded);
         }

      </script>
   </head>
   <body>
      <h1 id="myid">Sample Text</h1>

      <button onclick="getDataFromHtml()">Try Now!</button>
   </body>
</html>

点击“立即尝试!”按钮,看看魔法发生了什么!

1

这篇文章提供了如何向模板化HTML传递参数的解决方案:

html = HtmlService.createTemplateFromFile('page2');
html.id = s1;

然后在第二页使用 <?php echo $id; ?> 标签打印出 id 值;

<div class="info" >
    <span id="ID" name="ID"><?=id?></span>
</div>

1
这个解决方案是我认为官方的方式,但它有一个问题:当模板文件太大时,应用脚本会返回“超过最大堆栈深度”的错误。 - 0xh8h

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接