G Suite Business customers can preview App Maker. Ask your domain admin to apply for early access.

Client scripting

Client scripts run on users' computers, in their browsers' JavaScript runtimes. They're great for handling UI events and changing DOM elements and widget properties.

Client scripts can also trigger interactions with the server through App Maker APIs. For example, client scripts can fetch and modify data from a database or invoke server scripts.

Asynchronous operations

Client scripts often run asynchronously to keep your app's UI responsive.

In most browser apps, when an operation needs to wait on an external resource the entire app waits along with it, leading to an unresponsive UI. App Maker avoids this by allowing scripts to continue running while operations wait on external resources, or, in other words, run asynchronously. The result is that sometimes your scripts don't run in the order they're written.

To handle the result of asynchronous operations, App Maker APIs allow you to provide callback functions that run when the operations complete.

Here's an example of an asynchronous operation with a callback:

console.info("before execute");
google.script.run.withSuccessHandler(function(result) {
  console.info("received result");
}).sayHelloBack(name);
console.info("after execute");

In this example, sayHelloBack() is a server script, which runs asynchronously, and the argument to withSuccessHandler() is its callback. When the browser's JavaScript interpreter reaches sayHelloBack(), it schedules the callback for execution when the server responds and continues to execute the rest of the script. Here's the script's log:

before execute
after execute
received result

Types of callbacks

App Maker APIs allow for three ways to supply callbacks to asynchronous operations:

  • Omit the callback completely, or pass null as callback. The code continues without waiting for the operation to finish.

    widget.datasource.createItem();
    console.info("warning, record is probably not yet created!");
    
  • Pass a function for callback. If the operation succeeds, your callback function executes. If the operation fails, nothing special happens.

    widget.datasource.createItem(function (record) {
      record.MyField = 1;
    });
    
  • Pass an object that has success and failure functions. An extension of the previous case, use this method to be notified of failures and to perform error reporting, clean up, and/or roll backs.

    widget.datasource.createItem({
      success: function (record) {
        record.MyField = 1;  // executes if record was created
      },
      failure: function (error) {
        console.info("No new record for you!");
      }
    });
    

Troubleshooting

Chances are, there'll come a time in your client scripting experience when you'll run into errors. Here are some strategies for troubleshooting your client scripts to find and fix those errors:

  • Find syntax errors in the script editor. The App Maker script editor automatically warns you of syntax problems as you type. Check for warning labels to the left of your script's line numbers. The warnings catch common errors like forgotten parentheses.

  • Find runtime errors in your browser's JavaScript console. Use your browser's JavaScript console to find runtime errors. In Chrome, you can open it by pressing either Ctrl+Shift+j or Alt-Cmd-j. Some common runtime exceptions are null dereferencing or uncaught throw statements.

  • Follow your script's execution with logs and alerts. Use the browser's built-in console.log() function to send logs to the JavaScript console. Alternatively, use alert() to open an alert box that interrupts the execution of your script until you dismiss it.

Client script examples

Use scripts in binding expressions

You can use JavaScript to perform calculations in binding expressions. For example, the following expression in a button's enabled property disables the button if the Name field is blank, or the Age field has a value less than 18:

(@widget.parent.children.NameTextBox.value).length != 0 &&
@widget.parent.children.AgeTextBox.value >= 18

The App Maker parser first resolves the binding expressions, which start with @, to find the value of Name and Age. It then substitutes those values for the expressions, and evaluates the rest of the JavaScript.

The parentheses in the first line indicate the end of the binding expression. Without them, App Maker would watch the length property for changes, instead of the value property.

Call a server script

You can call server scripts from your client scripts using App Maker APIs. Calling server scripts is an asynchronous operation, so you have to use a callback to process the result of the script.

The client script below calls a server script to pull the current price of Google stock, then displays the result as an alert message using a callback.

Client script:

function getGoog() {
  google.script.run.withSuccessHandler(function(result) {
    var quote = JSON.parse(result.substring(3))[0]["l"];
    alert(quote);
  }).getGoogleStockInfo();
};

Server script:

function getGoogleStockInfo() {
  var url = "http://www.google.com/finance/info?q=NASDAQ:GOOG";
  var response = UrlFetchApp.fetch(url);
  return response.getContentText();
}

Dynamically load external scripts

You can load many third-party scripts synchronously with External JavaScript Libraries, but some require a little more work. Libraries that require a callback parameter, like the Google APIs Client Library need to be loaded dynamically, by adding a script to Settings > General > onAppStart.

The following script loads the Google+ library:

// Suspends app loading until after the Google Client API loads.
loader.suspendLoad();
// Defines a callback function, for the client API. It must be global,
// so it's explicitly attached to the window object.
window.OnGapiClientLoad = function() {
  // Uses the Google Client API to load the Google+ library.
  gapi.client.load("plus", "v1", function() {
    // Continues loading the app once Google+ loads.
    loader.resumeLoad();
  });
};

var script = document.createElement("script");
script.setAttribute("type", "text/javascript");

// Specifies the name of the callback function in the "onload"
// parameter of the URL.
var url = "https://apis.google.com/js/client.js?onload=OnGapiClientLoad";
script.setAttribute("src", url);

document.getElementsByTagName("head")[0].appendChild(script);

Call Google APIs

This final example is a little more in-depth, and should give you a feel for what an actual App Maker script looks like. It makes use of a Google JavaScript API.

The script displays a Google Map in a page and updates it based on user input. The script works with a page named Map that contains the following widgets:

  • Two text fields, named Street and Zip.
  • A 200x200 HtmlArea named MapDiv which has an onAttach event that calls loadMaps()
  • A button, with an onClick() handler that calls updateMap()

Script:

var map;
var geocoder;
var marker;

// Called by loadMaps() to set up widgets.
function createMap() {
  var div = app.views.Map.descendants.MapDiv.getElement();
  map = new google.maps.Map(div, {
    center: new google.maps.LatLng(-34.397, 150.644),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    zoom: 10,
  });
  geocoder = new google.maps.Geocoder();
  marker = new google.maps.Marker({map: map});
}

// Creates an address string from Street and Zip text fields.
function getAddress(addr) {
  var view = app.views.Map;
  var street = view.descendants.Street.value; // [2]
  var zip = view.descendants.Zip.value;
  return street + ", " + zip;
}

// Positions the map at the given location coordinates.
function showLocation(locations, status) {
  if (locations.length > 0) {
    var latLng = locations[0].geometry.location;
    map.panTo(latLng);
    marker.setPosition(latLng);
  }
}

// Sets up the Google Maps library.
function loadMaps() {
  google.load("maps", "3", {callback: createMap});
}

function updateMap() {
  geocoder.geocode({address: getAddress()}, showLocation);
}

Script highlights:

  • MapDiv uses its onAttach() handler to set up the Google Maps API.
  • The script uses getAddress() to access user input by pulling data from the Street and Zip fields.