3

I want to build multi-page sites using Google Apps Script. To do this, I would like to exploit the ability of doGet() to process query strings. I am aware of at least one solution to this problem:

Linking to another HTML page in Google Apps Script

However, that solution seems overly restrictive, since it relies on GAS's templated html feature to build the proper URL with query string right within the HTML at page load-time. A more general-purpose solution would be to build the appropriate query string within a jquery (or javascript) function based on dynamic UI state and user interface actions. Is this possible? How can it be done?

Here is my proposal for a possible solution (which doesn't work!):

HTML/JQuery:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <title>Speedgolf Scoring</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.11.1/themes/smoothness/jquery-ui.css" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
  </head>
  <body id = "mainBody">
    <H1>LOGIN PAGE</H1>
    <H3>Please choose interface:</H3>
    <button id = "playerUI">Follow Player</button>
    <button id = "holeUI">Follow Hole</button>
    <button id = "flexUI">Roaming Scorer</button>
  </body>
</html>

<script>

  function loadNewPage(result, userObject) {
    var newURL = result + userObject;
    alert("Newly-constructed URL to be loaded: " + newURL); //CORRECT URL IS CONSTRUCTED BASED ON DEBUGGING OUTPUT!
    window.open(newURL,"_self"); //<--THIS IS THE LINE THAT SHOULD INVOKE DOGET(). SERVER LOGS INDICATE DOGET() IS INVOKED, BUT NO PAGE IS VISIBLY SERVED.
  }

  $("#playerUI").click(function () {
  alert("In player UI click...");
  var newURL = "?mode=player";
  google.script.run.withSuccessHandler(loadNewPage).withUserObject(newURL).getScriptURL();
  //Execution continues in loadNewPage callback...
  });

  $("#holeUI").click(function () {
   var newURL = "?mode=hole";
   google.script.run.withSuccessHandler(loadNewPage).withUserObject(newURL).getScriptURL();
  });

   $("#flexUI").click(function () {
    var newURL = "?mode=flex";
    google.script.run.withSuccessHandler(loadNewPage).withUserObject(newURL).getScriptURL();
  });

</script>

And my Code.gs file:

function getScriptURL() {
  var url = ScriptApp.getService().getUrl();
  Logger.log('In getScriptURL...Value of URL returned: ' + url);
  return url;
}


function doGet(e) {
  Logger.log('In doGet');
  Logger.log('query params: ' + Utilities.jsonStringify(e));
  if (e.queryString === '') {
    Logger.log('Serving loginUI');
    return HtmlService
    .createHtmlOutputFromFile('loginUI')
    .setSandboxMode(HtmlService.SandboxMode.IFRAME)
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setTitle("Please log in");
  }
  //if here, we know query string is NOT null
  if (e.parameter.mode == "player") {
    Logger.log('Serving player UI');
    return HtmlService
    .createHtmlOutputFromFile('playerUI')
    .setSandboxMode(HtmlService.SandboxMode.IFRAME)
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setTitle("Following Player");
  }
  if (e.parameter.mode == "hole") {
    Logger.log('Serving hole UI');
    return HtmlService
    .createHtmlOutputFromFile('holeUI')
    .setSandboxMode(HtmlService.SandboxMode.IFRAME)
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setTitle("Following Hole");
  } 
  if (e.parameter.mode == "flex") {
    Logger.log('Serving flex UI');
    return HtmlService
    .createHtmlOutputFromFile('flexUI')
    .setSandboxMode(HtmlService.SandboxMode.IFRAME)
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setTitle("Flex Interface");
  }
}

I am able to verify, through server logs and alert boxes, that the correct URL with query string is built. I would expect this line of code:

$("#mainBody").load(newURL); 

to invoke doGet() and cause a new page to be loaded based on the query string. However, through my server logs, I observe that doGet() is not invoked, and hence the new page I expect to be served is never served. What is happening? Why is doGet() not invoked when I load a new page through jquery? How can I force doGet() to be invoked? Thanks in advance!

SpeedGolfer
  • 255
  • 3
  • 13

2 Answers2

4

Aha! The above code will work properly if you change

window.open(newURL,"_self");

to

window.open(newURL,"_top");

I am not sure why "_top" is necessary, but in the example code above, it makes all the difference: doGet() is invoked with the proper query string, resulting in the expected page being served. Hooray! This approach to serving new web pages on the fly provides a powerful alternative to templated HTML. Enjoy!

SpeedGolfer
  • 255
  • 3
  • 13
3

Here's what I ended up using in the basic html page:

<!DOCTYPE html>
<html>
  <head>
    <title>Speedgolf Scoring</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script>

  function loadNewPage(url) {
    //window.location.href = url;
    window.open(url,"_top");
  }

  function getUrl(qs)
  {
    google.script.run
        .withSuccessHandler(loadNewPage)
        .getScriptURL(qs);
  }

console.log('MyCode');
</script>
  </head>
  <body>
    <H3>Please choose interface:</H3>
    <input type="button" value="All Sheets" onClick="getUrl('?mode=allsheets');" />
    <input type="button" value="One Sheet" onClick="getUrl('?mode=onesheet');" />
    <input type="button" value="Return to Main" onClick="getUrl('');" />
  </body>
</html>

And my gs code now looks like this:

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 'onesheet':
        return HtmlService
        .createHtmlOutputFromFile('onesheet')
        .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
        .append('<br />onesheet')
        .setTitle("One Sheet"); 
        break;
      case 'allsheets':
         return HtmlService
         .createHtmlOutputFromFile('allsheets')
         .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
         .append('<br />allsheets')
         .setTitle("All Sheets");
        break;
      default:
         return HtmlService
         .createHtmlOutputFromFile('speedgolferah')
         .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
         .append('<br />No Query String')
         .setTitle("Main Page");
        break;
    }
  }
  else
  {
    return HtmlService
         .createHtmlOutputFromFile('speedgolferah')
         .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
         .append('<br />No Query String')
         .setTitle("Main Page");
  }
}

I have some other pages I threw in because I had them. One displays all of my sheets and one displays just the one I select. But yeh I'd been putting this off for a while and I was good to get in there and start messing with the multiple payge solution for the doGet again.

But thanks for the window.open(url,"_top"); that made a lot of difference. Thanks.

Cooper
  • 59,616
  • 6
  • 23
  • 54