EML
EML

Reputation: 10281

Creating 'dynamic' pages in web apps

I've just started playing around with apps-script and Google web apps. I can construct a basic app in which a 'static' (for want of a better word) page is set up as follows:

function doGet() {
var app = UiApp.createApplication().setTitle('foo');
...set up widgets/etc
return app;
}
...handlers here

As far as I can make out, none of the handlers start running until after doGet returns. And that's my problem. I need an interactive page, where the contents of the page are determined by the first response to a drop-down list. In other words, the user is presented with a drop list, he makes a selection and hits 'submit', and I then have to set up a list box based on that selection.

So, it looks like I can't put any of my logic in doGet, and I essentially have to chain all the logic through my event handlers:

function doGet() {
  ...set up first page and first submit handler
  return app;
}
function firstSubmitHandler(e) {
  .. respond to first submit handler, draw list boxes, set up second list handler
}
function secondSubmitHandler(e) {
  .. respond to second submit handler, yada
}

If this is right, it's crazy. Have I missed something?

Upvotes: 4

Views: 3832

Answers (3)

Alan Wells
Alan Wells

Reputation: 31300

HTML content can be retrieved and dynamically injected into a page without reloading the Google Apps Script Web Page.

Set up empty elements to receive new page content

  <section id="ChgViewSection">
      <div id="PageOne"></div>
      <div id="PageTwo"></div>
      <div id="PageThree"></div>
      <div id="PageFour"></div>
      <div id="PageFive"></div> 
  </section>

Initially set most elements to be hidden, style="display:none"

  <section id="ChgViewSection">
      <div id="PageOne" style="display:block"></div>
      <div id="PageTwo" style="display:none"></div>
      <div id="PageThree" style="display:none"></div>
      <div id="PageFour" style="display:none"></div>
      <div id="PageFive" style="display:none"></div>    
    </div>
  </section>

Set one element to style="display:block". That will be the page that is initially loaded.

Create a user interface to change the pages, a menu, or tabs.

<div id="tabs-nested-left">
  <div class="cb" id="tabSignIn" onclick="mainStart('SignInBody', 'SignIn')">Sign In</div>
  <div class="cb" id="tabInput" onclick="mainStart('InputBlock', 'InputForm')">Input</div>
  <div class="cb" id="tabReg" onclick="mainStart('RegisterBlock', 'Register')">Registration</div>
  <div class="cb" id="tabRegExpl" onclick="mainStart('Explain', 'Explain')">Registration Explanation</div>
  <div class="cb" id="tabContact" onclick="mainStart('ContactUs', 'Contact')">Security</div>
</div>

Write a <script> for handling the onclick event.

<script>

function mainStart(ElmtToGoTo, FileToGet) {
   var currntShown = 'SignInBody'; //Create variable and set initial value
   //Hide the currently shown element first
   document.getElementById(currntShown).style.display = 'none';
   //
   currntShown = ElmtToGoTo;
   //display the element that will be shown.  No content is injected yet
   document.getElementById(ElmtToGoTo).style.display = 'block';

   var el = document.getElementById(ElmtToGoTo);
   // If element is empty get HTML and inject
   if (el.childNodes.length === 0) {
      //alert("it's empty!");
    //Run back end google .gs code to get new content
    google.script.run.withFailureHandler(onFailure)
      .withSuccessHandler(onGotHTML)
      .include(FileToGet);
  };

};

<script>

Add failure and success handlers to you HTML script:

function onGotHTML(daHTML) {
  //alert("Got HTML File!");
  document.getElementById(currntShown).innerHTML=daHTML;
  };

function onFailure(error) {
  alert("on failure ran:" + error.message);
};

Write your server side .gs code:

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

When a tab is clicked, the currently shown <div> gets hidden, then the <div> to show is displayed, but no content is there yet. If the <div> already had content, do nothing. If the <div> is empty, run a google.script and get content from the server. Content is returned to the client and injected into the <div> with the success handler onGotHTML and document.getElementById(currntShown).innerHTML=daHTML;

Most of this is DOM manipulation and style changes. But what makes it possible is retrieving file content from the server. You need files saved with content to inject into the <div> elements. Retrieving HTML content from the server when the user clicks a menu item or tab avoids having to load everything up front. One problem is that CSS style data can NOT be added later unless it's inline (already in the tag). So you must either load all your CSS style files when the page first loads, or put style settings into the element. You can inject HTML after the page has loaded, and have it display without reloading the page, but you can't include CSS files after the page has loaded and have the new styles display.

I've probably left some things out, like CSS files for styling, but the code; the HTML/Style ; and overall strategy is shown. You can build a dynamic single page application like this.

Upvotes: 2

Corey G
Corey G

Reputation: 7858

In a webapp of any type, you respond to requests from the client, and then you are done. If the client wants interactivity it needs to make new requests. In most webapps this means making XHR requests to the server; apps script simplifies that with handlers, but the concept is identical.

Why is that crazy? That is how every webapp in the world is written - a server serves up an initial page, and if there is interactivity the page makes new requests and runs more things on the server. HTTP is designed to be a stateless protocol and even though there are extensions that allow for persistent connections between a client and server, they are used sparingly (for things like chat where you need instant updates) and would be unrealistically slow and expensive for what you are trying to do.

If you are coming to webapps from regular desktop programming, this model may be weird and unintuitive at first, but it's not special to Apps Script.

Upvotes: 3

Mogsdad
Mogsdad

Reputation: 45720

Here's a minor extension of the example script you get when you create a new "Script as Web App". All I've done is have the initial click handler add another button to the Ui, which then brings another click handler into scope. You can use the same concept to build a dynamic UI.

// Script-as-app template, extended.
// doGet is exactly as supplied
function doGet() {
  var app = UiApp.createApplication();

  var button = app.createButton('Click Me');
  app.add(button);

  var label = app.createLabel('The button was clicked.')
                 .setId('statusLabel')
                 .setVisible(false);
  app.add(label);

  var handler = app.createServerHandler('myClickHandler');
  handler.addCallbackElement(label);
  button.addClickHandler(handler);

  return app;
}

// myClickHandler now contains a modified copy of doGet.
function myClickHandler(e) {
  //////////// Key concept: The UI app lives on after doGet exits
  var app = UiApp.getActiveApplication();

  var button = app.createButton('Click Me 2');
  app.add(button);

  var label = app.createLabel('The 2 button was clicked.')
                 .setId('statusLabel')
                 .setVisible(false);
  app.add(label);

  var handler = app.createServerHandler('myClickHandler2');
  handler.addCallbackElement(label);
  button.addClickHandler(handler);

  return app;
}

// myClickHandler2 is the original myClickHandler, as supplied
function myClickHandler2(e) {
  var app = UiApp.getActiveApplication();

  var label = app.getElementById('statusLabel');
  label.setVisible(true);

  app.close();
  return app;
}

If that's not enough to get you started, take a look at a clear example of how to use Google UI Builder and Apps script, and the example supplied there by Serge.

Upvotes: 1

Related Questions