如何将参数传递给html?

时间:2022-07-20 16:37:33

I have a script that uses the file picker but I need to pass a specific parameter which is called userId and is kept as a global variable in the calling script. As the calls are asynchronous it seems I cannot access this parameter. Is there away to access the parameter from the html file or pass this parameter to the html?

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

I might be mixing templated html and non templated (i.e. https://developers.google.com/apps-script/guides/html/templates and https://developers.google.com/apps-script/guides/html/) but I need to solve this issue.

我可能会混合模板化的html和非模板化的(例如https://developers.google.com/apps-script/guides/html/templates和https://developers.google.com/apps-script/guides/html/)但我需要解决这个问题。

Appreciate any help.

感谢任何帮助。

Here is the calling code (initiated through a menu item in a spreadsheet):

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

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

Here is the html picker file:

这是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>

Here is the picker code:

这是选择器代码:

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
}

4 个解决方案

#1


The safest way is to pass the needed data to the HTML directly. If you use properties or cache service it can get complex or fail under multiple simultaneous users.
There are many techniques to pass an initial object from the server (.gs) to the client (.html).

最安全的方法是直接将所需数据传递给HTML。如果使用属性或缓存服务,它可能会在多个并发用户下变得复杂或失败。有许多技术可以将初始对象从服务器(.gs)传递到客户端(.html)。

Using HtmlTemplate, you may do:
//.gs file

使用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;
}

and in your template-client.html file:

并在您的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>

It is also possible to do it without using templating, by appending a hidden div to an HtmlOutput:

通过将隐藏的div附加到HtmlOutput,也可以在不使用模板的情况下执行此操作:

//.gs file:

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://*.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);
}

and in your output-client.html:

并在您的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>


Both methods are compared and better explained in this little github I made: https://github.com/zmandel/htmlService-get-set-data

在我制作的这个小github中比较和更好地解释了这两种方法:https://github.com/zmandel/htmlService-get-set-data

#2


I often use HtmlService templates to push static values to the client.

我经常使用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]);

#3


In your code:

在你的代码中:

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

You are assigning a value to the global variable named sUserId. But, then when you try to retrieve it, nothing is there. Global variables loose their value as soon as the current instance of the code bring run is completed. Global variable don't persist their values.

您正在为名为sUserId的全局变量赋值。但是,当你试图检索它时,什么都没有。一旦当前的代码实例运行完成,全局变量就会失去它们的值。全局变量不会持久存在它们的值。

You'll need to use the Properties Service to store the value. Or you could use the Cache service. If you want the value of the user id to expire after some time, use cache service.

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

#4


By Appending to the HTML File, as shown below.

通过附加到HTML文件,如下所示。

within 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>

Click the 'Try Now!' Button and See the Magic!

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

#1


The safest way is to pass the needed data to the HTML directly. If you use properties or cache service it can get complex or fail under multiple simultaneous users.
There are many techniques to pass an initial object from the server (.gs) to the client (.html).

最安全的方法是直接将所需数据传递给HTML。如果使用属性或缓存服务,它可能会在多个并发用户下变得复杂或失败。有许多技术可以将初始对象从服务器(.gs)传递到客户端(.html)。

Using HtmlTemplate, you may do:
//.gs file

使用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;
}

and in your template-client.html file:

并在您的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>

It is also possible to do it without using templating, by appending a hidden div to an HtmlOutput:

通过将隐藏的div附加到HtmlOutput,也可以在不使用模板的情况下执行此操作:

//.gs file:

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://*.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);
}

and in your output-client.html:

并在您的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>


Both methods are compared and better explained in this little github I made: https://github.com/zmandel/htmlService-get-set-data

在我制作的这个小github中比较和更好地解释了这两种方法:https://github.com/zmandel/htmlService-get-set-data

#2


I often use HtmlService templates to push static values to the client.

我经常使用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]);

#3


In your code:

在你的代码中:

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

You are assigning a value to the global variable named sUserId. But, then when you try to retrieve it, nothing is there. Global variables loose their value as soon as the current instance of the code bring run is completed. Global variable don't persist their values.

您正在为名为sUserId的全局变量赋值。但是,当你试图检索它时,什么都没有。一旦当前的代码实例运行完成,全局变量就会失去它们的值。全局变量不会持久存在它们的值。

You'll need to use the Properties Service to store the value. Or you could use the Cache service. If you want the value of the user id to expire after some time, use cache service.

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

#4


By Appending to the HTML File, as shown below.

通过附加到HTML文件,如下所示。

within 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>

Click the 'Try Now!' Button and See the Magic!

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