在Google Apps Script中链接到另一个HTML页面

25

在从ScriptDbConsole.html链接到legend.html时,我收到以下错误消息:

对不起,您请求的文件不存在。请检查地址并重试。

在正常环境下,这通常可以正常工作,但我想在script.google.com上不行。

在script.google.com项目中创建一个新的 .html 文件时,它会将其创建在与其他文件相同的位置,因此这段代码应该可以正常工作,对吗?我该如何从ScriptDbConsole.html打开legend.html?

<a href='legend.html' target='_blank'>Open in new window</a>

3个回答

63

虽然HtmlService允许您提供HTML,但它并不是“托管”页面,您不能直接通过URL访问应用脚本项目中的各种html文件。 相反,只有在发布Web应用程序时,您的Web应用程序将具有一个URL,而这是您唯一拥有的URL。

以下是一种通过脚本提供单独页面并使其类似于HTML文件链接的方法。

当调用 doGet() 函数时,会传递一个事件,并且我们可以利用它来指示要提供哪个页面。 如果我们的Web应用程序ID是<SCRIPTURL>,那么URL加上请求特定页面的查询字符串如下:

https://script.google.com/macros/s/<SCRIPTURL>/dev?page=my1

使用模板化的HTML,我们可以动态生成所需的URL和查询字符串。在我们的doGet()方法中,我们只需要解析查询字符串以确定要提供哪个页面。

以下是带有两个包含按钮以在它们之间切换的示例页面的脚本。

Code.gs

/**
 * Get the URL for the Google Apps Script running as a WebApp.
 */
function getScriptUrl() {
 var url = ScriptApp.getService().getUrl();
 return url;
}

/**
 * Get "home page", or a requested page.
 * Expects a 'page' parameter in querystring.
 *
 * @param {event} e Event passed to doGet, with querystring
 * @returns {String/html} Html to be served
 */
function doGet(e) {
  Logger.log( Utilities.jsonStringify(e) );
  if (!e.parameter.page) {
    // When no specific page requested, return "home page"
    return HtmlService.createTemplateFromFile('my1').evaluate();
  }
  // else, use page parameter to pick an html file from the script
  return HtmlService.createTemplateFromFile(e.parameter['page']).evaluate();
}

我的1.html

<html>
  <body>
    <h1>Source = my1.html</h1>
    <?var url = getScriptUrl();?><a href='<?=url?>?page=my2'> <input type='button' name='button' value='my2.html'></a>
  </body>
</html>

我的2.html

<html>
  <body>
    <h1>Source = my2.html</h1>
    <?var url = getScriptUrl();?><a href='<?=url?>?page=my1'> <input type='button' name='button' value='my1.html'></a>
  </body>
</html>

3
那很肮脏......但同时也很美丽。至少它还能发挥作用。 - Jon
3
我已找到解决上述错误的方法:您需要将以下内容添加到my1.html和my.html中:<head> <base target="_top"> </head> 这样,您的my1.html将如下所示:<html> <head> <base target="_top"> </head> <body> <h1>Source = my1.html</h1> <?var url = getScriptUrl();?><a href='<?=url?>?page=my2'> <input type='button' name='button' value='my2.html'></a> </body> </html> - Mark
这可能是一个愚蠢的问题...在我的HTML中,我已经粘贴了_<?var url = getScriptUrl();?><a href='<?=url?>?page=page2'> <input type='button' name='button' value='page2.html'></a>。在我的页面上,<?var url = getScriptUrl();?>部分实际上显示出来了,并且旁边有一个按钮,但它会导致400错误,因为<?var url = getScriptUrl();?>_没有解析。为什么它会显示出来而不是调用函数? - Andrew
重要提示:此code.gs仍需要来自https://developers.google.com/apps-script/guides/html/best-practices的include函数。 - TulsaNewbie

6

多页面Web应用

这是一个基本的四页Web应用程序。它只显示页面标题和左侧边栏上用于导航的四个按钮。

Code.gs:

function getScriptURL(qs) {
  var url = ScriptApp.getService().getUrl();
  //Logger.log(url + qs);
  return url + qs ;
}

function doGet(e) 
{
  //Logger.log('query params: ' + Utilities.jsonStringify(e));
  if(e.queryString !=='')
  {  
    switch(e.parameter.mode)
    {
      case 'page4':
        setPage('Page4')      
        return HtmlService
        .createTemplateFromFile('Page4')
        .evaluate()
        .addMetaTag('viewport', 'width=device-width, initial-scale=1')
        .setTitle("Page4"); 
        break;  
      case 'page3':
        setPage('Page3');        
        return HtmlService
        .createTemplateFromFile('Page3')
        .evaluate()
        .addMetaTag('viewport', 'width=device-width, initial-scale=1')
        .setTitle("Page3");
        break;
      case 'page2':
        setPage('Page2');        
        return HtmlService
        .createTemplateFromFile('Page2')
        .evaluate()
        .addMetaTag('viewport', 'width=device-width, initial-scale=1')
        .setTitle("Page2");
        break;  
      case 'page1':
         setPage('Page1');
         return HtmlService
        .createTemplateFromFile('Page1')
        .evaluate()
        .addMetaTag('viewport', 'width=device-width, initial-scale=1')
        .setTitle("Page1");
        break;
      default:
        setPage('Page1');
        return HtmlService
        .createTemplateFromFile('Page1')
        .evaluate()
        .addMetaTag('viewport', 'width=device-width, initial-scale=1')
        .setTitle("Page1");
        break;
    }
  }
  else
  {
    setPage('Page1');
    return HtmlService
    .createTemplateFromFile('Page1')
    .evaluate()
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setTitle("Page1");
  }
}

function getPageData()
{
  var s='';
  s+='<input type="button" value="Page1" onClick="getUrl(\'?mode=page1\');" />';
  s+='<br /><input type="button" value="Page2" onClick="getUrl(\'?mode=page2\');" />';
  s+='<br /><input type="button" value="Page3" onClick="getUrl(\'?mode=page3\');" />';
  s+='<br /><input type="button" value="Page4" onClick="getUrl(\'?mode=page4\');" />';
  var rObj={menu:s,title:getPage()};
  Logger.log(rObj);
  return rObj;
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

Pages.gs

我决定切换到用户脚本属性服务,因为您可能想要拥有多个用户。我经常开发只供我使用的脚本。

function setPage(page) {
  var ps=PropertiesService.getUserProperties();
  ps.setProperty('PageTitle', page);
  return ps.getProperty('PageTitle');
}

function initPage() {
  var ps=PropertiesService.getUserProperties();
  ps.setProperty('PageTitle','');
  return ps.getProperty('PageTitle');
}

function getPage() {
  var ps=PropertiesService.getUserProperties();
  var pt=ps.getProperty('PageTitle');
  return pt;
}

globals.gs:

我曾经用这个来设置页面标题,但是我意识到有些人可能希望有多个用户,所以用户属性服务是更加合理的选择。我可能会用它来做其他事情,所以我把它留下了。但这由你来决定。

function getGlobals(){
  var ss=SpreadsheetApp.getActive();
  var sh=ss.getSheetByName('Globals');
  var rg=sh.getRange(1,1,getGlobalHeight(),2);
  var vA=rg.getValues();
  var g={};
  for(var i=0;i<vA.length;i++){
    g[vA[i][0]]=vA[i][1];
  }
  return g;
}

function setGlobals(dfltObj){
  if(dfltObj){
    var ss=SpreadsheetApp.getActive();
    var sh=ss.getSheetByName('Globals');
    var rg=sh.getRange(1,1,getGlobalHeight(),2);
    var vA=rg.getValues();
    for(var i=0;i<vA.length;i++){
      vA[i][1]=dfltObj[vA[i][0]];
    }
    rg.setValues(vA);
  }
}

function getGlobal(key) {
  var rObj=getGlobals();
  if(rObj.hasOwnProperty(key)){
    return rObj[key];
  }else{
    throw(Utilities.formatString('JJE-SimpleUtilitiesScripts-Error: Globals does not contain a key named %s.',key));
  }  
}

function setGlobal(key,value){
  var curObj=getGlobals();
  curObj[key]=value;
  setGlobals(curObj);
}

function getGlobalHeight(){
  var ss=SpreadsheetApp.getActive();
  var sh=ss.getSheetByName('Globals');
  var rg=sh.getRange(1,1,sh.getMaxRows(),1);
  var vA=rg.getValues();
  for(var i=0;i<vA.length;i++){
    if(!vA[i][0]){
      break;
    }
  }
  return i;
}

第1页.html:

1/4.它们一开始都是相同的。我会按照这种方式制作它们,如果需要自定义它们,通常会直接在html页面内进行操作。但也可以添加额外的JavaScript或CSS页面以支持每个页面之间的变化。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= include('res') ?>
    <?!= include('css') ?>
  </head>
  <body>
    <?!= include('content') ?>
    <?!= include('script') ?>
  </body>
</html>
<html><head>

content.html:

    <div class="sidenav"></div>
    <div class="header">
      <h1 id="ttl"></h1>
    </div>
    <div class="main"></div>

script.html:

<script>
  $(function(){
    google.script.run
    .withSuccessHandler(updatePageData)
    .getPageData();
  });
    function getUrl(qs){
      google.script.run
      .withSuccessHandler(loadNewPage)
      .getScriptURL(qs);
    }
     function updatePageData(dObj){
      $('.sidenav').html(dObj.menu);
      $('.header #ttl').html(dObj.title);
    }
    function loadNewPage(url){
      window.open(url,"_top");
    }
    console.log('script.html');
</script>

css.html:

一个非常简单的样式页面。

<style>
input[type="button"],input[type="text"],label{margin:2px 2px 5px 5px;}
body {
  background-color:#fbd393;
  font-family: "Lato", sans-serif;
}
.sidenav {
  height: 100%;
  width: 75px;
  position: fixed;
  z-index: 1;
  top: 0;
  left: 0;
  background-color: #EEE;
  overflow-x: hidden;
  padding-top: 5px;
}

.sidenav a {
  padding: 6px 8px 6px 16px;
  text-decoration: none;
  font-size: 16px;
  color: #818181;
  display: block;
}
.sidenav a:hover,label:hover {
  color: #770000;
}
.header{
  margin-left: 75px; /* Same as the width of the sidenav */
  font-size: 16px; 
  padding: 0px 5px;
  background-color:#fbd393;
  height:60px;
}

.main {
  margin-left: 75px; /* Same as the width of the sidenav */
  font-size: 16px; /* Increased text to enable scrolling */
  padding: 0px 5px;
  background-color:#e9e8de;
  height:450px;
}

@media screen and (max-height: 450px) {
  .sidenav {padding-top: 5px;}
  .sidenav a {font-size: 16px;}
}

</style>

res.html:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

动画:

这里输入图片描述


6

谷歌应用脚本Web应用程序主要设计为单页面Web应用程序,具有动态页面。但它也可以用作多页面应用程序(不建议这样做)。这是一个示例Web应用程序,它使用更改处理器使用URL哈希片段获取Web页面(而不是前面的答案中的模板)。

Code.gs:

//@return Base Url
function getUrl() {
  return ScriptApp.getService().getUrl()
}
//@return Html page raw content string
function getHtml(hash) {
  return HtmlService.createHtmlOutputFromFile(hash).getContent()
}

//@return provided page in the urlquery '?page=[PAGEID]' or main index page
function doGet(e) {
  var page = e.parameter.page
  return HtmlService.createHtmlOutputFromFile(page || 'index')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setTitle('App Demo')
}

page1.html

<h3>This is Page 1</h3>
<p>Hello World!</p>

page2.html

<h4>This is Page2</h4>
<p>Goodbye World!</p>

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top" />
    <title>Single Page App</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <style>
      h1 {
        text-align: center;
        margin: 2px;
        text-transform: uppercase;
        background-color: green;
      }
      span:hover,
      a:hover {
        background-color: yellowgreen;
      }
      body {
        background-color: brown;
        color: white;
        font-size: 2em;
      }
      a:visited {
        color: white;
      }
    </style>
  </head>
  <body>
    <h1><span id="type">Single</span> Page App Demo</h1>
    <div id="main">Loading...</div>
    <script>
      //Change base url
      google.script.run
        .withSuccessHandler(url => {
          $('base').attr('href', url)
        })
        .getUrl()

      //Function to handle hash change
      function change(e) {
        let hash = e.location.hash
        if (!hash) {
          main()
          return
        }
        google.script.run
          .withSuccessHandler(htmlFragment => {
            $('#main').html(htmlFragment)
          })
          .getHtml(hash)
      }
      google.script.history.setChangeHandler(change)

      //Function to add Main page html
      function main() {
        $('#main').html(`
            <ul>
              <li><a href="#page1">Page1</a></li>
              <li><a href="#page2">Page2</a></li>
            </ul>`)
      }

      //Loads Main html from main function
      //Adds toggle to span to change to a Multiple page app
      $(() => {
        main()
        $('#type').on('click', () => {
          let hf = $('a').attr('href')
          if (!hf) return
          hf = hf.indexOf('#') + 1
          $('#type').text(hf ? 'Multiple' : 'Single')
          $('a').each((i, el) => {
            $(el).attr('href', (i, v) =>
              hf ? '?page=' + v.slice(1) : '#' + v.slice(6)
            )
          })
        })
      })
    </script>
  </body>
</html>

参考资料:


1
干得好,非常感谢。这是一个实时演示:[https://script.google.com/macros/s/AKfycbx1N1N0cL92hC-c1r70HyhnztaPdReX6vUCM2MiaD8dHHpGfZfH_UiVuMYe8qml_EWa5w/exec]。 - Muhammet Yunus
1
@MuhammetYunus 希望你尝试点击顶部的“Single”! - TheMaster

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