【问题标题】:How to pass a parameter to html?如何将参数传递给html?
【发布时间】:2015-07-14 00:50:49
【问题描述】:

我有一个使用文件选择器的脚本,但我需要传递一个名为 userId 的特定参数,并在调用脚本中作为全局变量保存。由于调用是异步的,我似乎无法访问此参数。有没有办法从html文件中访问参数或者把这个参数传给html?

我可能会混淆templated htmlnon templated

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

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
}

【问题讨论】:

    标签: html google-apps-script client-server google-picker html-templates


    【解决方案1】:

    最安全的方法是将需要的数据直接传递给 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>
    

    也可以不使用模板,通过将隐藏的 div 附加到 HtmlOutput:

    //.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 http://stackoverflow.com/questions/6008047/why-dont-people-use-base128
        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)
    • 作为补充,请阅读stackoverflow.com/a/67861065/1636522
    • 对我来说了解 html 脚本标签的好方法。谢谢!
    【解决方案2】:

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

    index.html

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

    代码.gs

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

    【讨论】:

    • 谢谢你,但是如何将模板 html 与我的 html 代码结合使用,这样它才能工作?
    • 你的问题是如何将参数传递给html。您的代码表明您了解 google.script.run,所以我向您展示了模板方法。请修改并澄清您的问题。
    • 我正在尝试将两者结合起来似乎没有任何效果,因此正在寻找传递参数。 .evaluate 在 html 上(在 syncStudentsFile 中)不断返回错误。我不是关于如何使用这个 html 的专家。因此我的问题。
    • 这个解决方案是我认为的官方方式,但是有一个问题:当模板文件太大时,apps脚本会返回“Exceeded maximum stack depth”错误。
    【解决方案3】:

    在您的代码中:

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

    您正在为名为@9​​87654324@ 的全局变量赋值。但是,当您尝试检索它时,什么都没有。一旦代码运行的当前实例完成,全局变量就会失去它们的值。全局变量不会保留它们的值。

    您需要使用Properties Service 来存储该值。或者您可以使用Cache service。如果您希望用户 id 的值在一段时间后过期,请使用缓存服务。

    【讨论】:

    • 缓存似乎不起作用,但我会尝试一下属性服务...如果可以传递参数会更容易。谢谢!
    【解决方案4】:

    通过追加到 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>
    

    点击“立即尝试!”按钮,看看魔法!

    【讨论】:

      【解决方案5】:

      This post 提供了如何将参数传递给模板化 html 的解决方案:

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

      然后在第2页使用标签打印出id值;

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

      【讨论】:

      • 这个解决方案是我认为的官方方式,但是有一个问题:当模板文件太大时,apps脚本会返回“Exceeded maximum stack depth”错误。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      • 2020-10-09
      • 2012-12-21
      • 2012-01-07
      • 2014-05-03
      • 2015-06-26
      相关资源
      最近更新 更多