The UI service in Google Apps Script provides the ability to build a user interface for displaying or capturing information using user interface elements called widgets.
- Overview
- Available user interface elements
- Designing the user interface
- Creating user interface elements
- Using server handlers
- Using client handlers for more responsive UIs
- Using validators
- Using forms
- Deploying a user interface as a web app
- Displaying a user interface from a spreadsheet
- Using custom HTML
Overview
Your scripts can create a wide range of user interface elements, including, but not limited to, the following:
- Push buttons
- Radio buttons
- Toggle buttons
- Check boxes
- Text fields
- Labels
- Titles
- List boxes
- Dialog boxes
- Panels of many types
- And far more elements than it's possible to list here. The section Viewing the Available User Interface Elements tells you how to see all the available elements. You can also review the API documentation for this feature.
After you put these elements together, the result is a working user interface. Your scripts can display the user interface in the following three ways:
- As a stand-alone web app
- Embedded in a Site
- Directly from a spreadsheet
Available user interface elements
You can create a large number of user interface elements using Google Apps Script.
To see all the available elements:
- Open the Script Editor
- Type in the following instructions; do not copy and paste:
function myFunction() { var app = UiApp.createApplication(); var button = app. }
You see a menu listing all available elements that you can create, as well as the arguments required for each element. If you don't see a menu listing, it may be because you pasted in the code. The built-in code autocomplete functionality in the editor requires you to type the trailing period that followsapp
.
You can also see all the available methods for creating user interface elements in the documentation for the UiInstance
class.
Designing the user interface
Before you start writing the script to display the user interface, do the following:
- Plan the script. What tasks should the script accomplish?
- Write down the specific information you want to display to or collect from your users.
- Draw the user interface and make notes about all the interface elements you must create with your script.
- Determine what the script and interface should do in response to any user input.
- Determine the conditions for exiting the script.
Creating user interface elements
Before you can create user interface elements such as buttons or dialog boxes,
you need a
UiInstance
object to contain the user interface elements. After
you create the UiInstance
object, you can add buttons, dialog
boxes, panels, and other elements to the UiInstance
object.
The general syntax for these operations is as follows:
- To create a
UiInstance
object, use the syntaxvar your_app_name = UiApp.createApplication();
, where your_app_name is the name you assign. - To create a user interface element and associate it with your
UiInstance
object, use the syntax varyour_ui_element_name= your_app_name.createElement_Name();
. - To add one user interface element to another, for example to display
a button on a panel, use the syntax
your_ui_element_name1.add(your_ui_element_name2);
The following statement creates an UiInstance
object called
myapp
by calling the createApplication
method of the
UiApp
class:
var myapp = UiApp.createApplication();
You can now create some user interface elements. Here's a button with the text Press Me on it:
var mybutton = myapp.createButton('Press Me');
The text you want displayed on a button is passed in as an argument.
You might want a panel to hold the button. The following creates a vertical panel.
var mypanel = myapp.createVerticalPanel();
There are other kinds of panels, too: stack panels, focus panels, form panels, and so on.
Here's the code for displaying your button on the panel:
mypanel.add(mybutton);
Next, add the panel to the application:
myapp.add(mypanel);
Lastly, you need to instruct Google Apps Script to display the interface elements:
return myapp;
You can create the user interface elements in any order. The order in which
you add elements to the objects determines the display order. For example, if
you create a VerticalPanel
object, and add elements a, b and c to the panel, the elements are displayed
from top to bottom in that order. However, you can create a, b, and c in any
order. Creating the elements and adding them to your application are separate
steps requiring separate instructions.
Here's a short script that does nothing but display a panel with a button on it. You can follow what each line does in the comments.
function doGet() { // A script with a user interface that is published as a web app // must contain a doGet(e) function. // Create the UiInstance object myapp and set the title text var myapp = UiApp.createApplication().setTitle('Here is the title bar'); // Create a button called mybutton and set the button text var mybutton = myapp.createButton('Here is a button'); // Create a vertical panel called mypanel and add it to myapp var mypanel = myapp.createVerticalPanel(); // Add mybutton to mypanel mypanel.add(mybutton); // Add my panel to myapp myapp.add(mypanel); // return myapp to display the UiInstance object and all elements associated with it. return myapp; }
You can chain together setter methods, which are methods that set
values on objects. In the script above, the instruction
var myapp = UiApp.createApplication().setTitle('Here is the title
bar');
creates a
UiInstance
object and sets its title. You might also set the size of the object:
var myapp = UiApp.createApplication().setTitle('Here is the title bar').setHeight(50).setWidth(100);
Here's another short script that displays a panel but doesn't do anything
else. It shows how to use
Grid
objects and the
setWidget
method to create a more complex layout and also how to create text boxes and
label them. You can enter text into the text boxes, but pressing the button
does nothing.
The grid size of 3 x 2 is defined in the instruction
var mygrid = myapp.createGrid(3, 2);
and each
setWidget
method defines the element displayed at each position within the grid. Note
that the position definitions use a zero-based index, so that rows are
identified with the numerals 0, 1, and 2 rather than 1, 2, and 3.
function demoUI() { var myapp = UiApp.createApplication().setTitle('An improved GUI'); var mygrid = myapp.createGrid(3, 2); mygrid.setWidget(0, 0, myapp.createLabel('Name:')); mygrid.setWidget(0, 1, myapp.createTextBox()); mygrid.setWidget(1, 0, myapp.createLabel('Age:')); mygrid.setWidget(1, 1, myapp.createTextBox()); mygrid.setWidget(2, 0, myapp.createLabel('City')); mygrid.setWidget(2, 1, myapp.createTextBox()); var mybutton = myapp.createButton('Press me'); var mypanel = myapp.createVerticalPanel(); mypanel.add(mygrid); mypanel.add(mybutton); myapp.add(mypanel); return myapp; }
Using server handlers
The two scripts in the section Creating User Interface Elements display a panel with various controls, but they don't do anything else. To make a user interface useful, you need the ability to accept input from a user and update the user interface.
Here's a simple example that responds to a button being clicked by making a label visible.
function doGet(e) { 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'); button.addClickHandler(handler); return app; } function myClickHandler(e) { var app = UiApp.getActiveApplication(); var label = app.getElementById('statusLabel'); label.setVisible(true); app.close(); return app; }
The doGet
function is the one that is run when a user accesses the URL at which the script is deployed. Though it's not used in this simple example, notice that doGet
takes a parameter, e
. This e
parameter contains any information that was passed in the URL's query string. For more details, see doGet Parameters below.
var handler = app.createServerHandler('myClickHandler'); button.addClickHandler(handler);
The instruction
var handler = app.createServerHandler('myClickHandler');
creates a server-side click handler object called
handler
as part of the
UiInstance
application called
app
. A click handler performs an action in response to a mouse click. Server-side
means that the actions are performed by a server, in this case Google's Apps
Script server. The argument
('myClickHandler'
) means the handler action is to run
function myClickHandler()
.
You can add multiple click handlers to a button. The calls to the click handlers are asynchronous. That means that after the browser has requested that the server run a click handler function, it doesn't wait for a response, and continues immediately to the next line of code. This is important, because browser JavaScript is single-threaded, and if the code waited for the server to respond, nothing else could happen until the response came back, including users clicking on buttons in the app or doing anything else. This also means that server function calls may not execute in the order you expect.
The instruction
button.addClickHandler(handler);
associates the click handler to the already-defined button object called
button
.
By calling setId('statusLabel')
on the label
widget, we will be able to access it by its id in the myClickHandler
, as you'll see below.
The second function in the script,
myClickHandler()
, gets a reference to the active application, and then gets access to the label whose id we set to statusLabel
in the doGet
function. Once the function has access to that label, it can call setVisible(true)
to display it. Then the function closes the
application
app
, and, finally, return the script to its starting point.
Here's a longer and more complex script. This script collects some information from text fields on a panel and writes that information into a Spreadsheet. See the inline comments for details.
function doGet(e) { var doc = SpreadsheetApp.openById(SPREADSHEET_ID_GOES_HERE); var app = UiApp.createApplication().setTitle('New app'); // Create a grid with 3 text boxes and corresponding labels var grid = app.createGrid(3, 2); grid.setWidget(0, 0, app.createLabel('Name:')); // Text entered in the text box is passed in to userName // The setName method will make those widgets available by // the given name to the server handlers later grid.setWidget(0, 1, app.createTextBox().setName('userName')); grid.setWidget(1, 0, app.createLabel('Age:')); grid.setWidget(1, 1, app.createTextBox().setName('age')); // Text entered in the text box is passed in to age grid.setWidget(2, 0, app.createLabel('City')); grid.setWidget(2, 1, app.createTextBox().setName('city')); // Text entered in the text box is passed in to city. // Create a vertical panel.. var panel = app.createVerticalPanel(); // ...and add the grid to the panel panel.add(grid); // Create a button and click handler; pass in the grid object as a callback element and the handler as a click handler // Identify the function b as the server click handler var button = app.createButton('submit'); var handler = app.createServerHandler('b'); handler.addCallbackElement(grid); button.addClickHandler(handler); // Add the button to the panel and the panel to the application, then display the application app panel.add(button); app.add(panel); return app; } // Function that records the values in a spreadsheet function b(e) { var doc = SpreadsheetApp.openById(SPREADSHEET_ID_GOES_HERE); var lastRow = doc.getLastRow(); // Determine the last row in the Spreadsheet that contains any values var cell = doc.getRange('a1').offset(lastRow, 0); // determine the next free cell in column A // You can access e.parameter.userName because you used setName('userName') above and // also added the grid containing those widgets as a callback element to the server // handler. cell.setValue(e.parameter.userName); // Set the value of the cell to userName cell.offset(0, 1).setValue(e.parameter.age); // Set the value of the adjacent cell to age cell.offset(0, 2).setValue(e.parameter.city); // set the value of the next cell to city // Clean up - get the UiInstance object, close it, and return var app = UiApp.getActiveApplication(); app.close(); // The following line is REQUIRED for the widget to actually close. return app; }
Here's the same script, with functions added that enable the form to be used multiple times before a user chooses to exit. The in-line comments explain the new instructions.
function doGet(e) { var doc = SpreadsheetApp.openById(SPREADSHEET_ID_GOES_HERE); var app = UiApp.createApplication().setTitle('New app'); // Create the entry form, a 3 x 2 grid with text boxes for name, age, and city that is then added to a vertical panel var grid = app.createGrid(3, 2); grid.setWidget(0, 0, app.createLabel('Name:')); grid.setWidget(0, 1, app.createTextBox().setName('userName').setId('userName')); grid.setWidget(1, 0, app.createLabel('Age:')); grid.setWidget(1, 1, app.createTextBox().setName('age').setId('age')); grid.setWidget(2, 0, app.createLabel('City')); grid.setWidget(2, 1, app.createTextBox().setName('city').setId('city')); // Create a vertical panel and add the grid to the panel var panel = app.createVerticalPanel(); panel.add(grid); // Here's where this script diverges from the previous script. // We create a horizontal panel called buttonPanel to hold two buttons, one for submitting the contents of the form // to the Spreadsheet, the other to close the form. var buttonPanel = app.createHorizontalPanel(); // Two buttons get added to buttonPanel: button (for submits) and closeButton (for closing the form) // For the submit button we create a server click handler submitHandler and pass submitHandler to the button as a click handler. // the function submit gets called when the submit button is clicked. var button = app.createButton('submit'); var submitHandler = app.createServerClickHandler('submit'); submitHandler.addCallbackElement(grid); button.addClickHandler(submitHandler); buttonPanel.add(button); // For the close button, we create a server click handler closeHandler and pass closeHandler to the close button as a click handler. // The function close is called when the close button is clicked. var closeButton = app.createButton('close'); var closeHandler = app.createServerClickHandler('close'); closeButton.addClickHandler(closeHandler); buttonPanel.add(closeButton); // Create label called statusLabel and make it invisible; add buttonPanel and statusLabel to the main display panel. var statusLabel = app.createLabel().setId('status').setVisible(false); panel.add(statusLabel); panel.add(buttonPanel); app.add(panel); return app; } // Close everything return when the close button is clicked function close() { var app = UiApp.getActiveApplication(); app.close(); // The following line is REQUIRED for the widget to actually close. return app; } // function called when submit button is clicked function submit(e) { // Write the data in the text boxes back to the Spreadsheet var doc = SpreadsheetApp.openById(SPREADSHEET_ID_GOES_HERE); var lastRow = doc.getLastRow(); var cell = doc.getRange('a1').offset(lastRow, 0); cell.setValue(e.parameter.userName); cell.offset(0, 1).setValue(e.parameter.age); cell.offset(0, 2).setValue(e.parameter.city); // Clear the values from the text boxes so that new values can be entered var app = UiApp.getActiveApplication(); app.getElementById('userName').setValue(''); app.getElementById('age').setValue(''); app.getElementById('city').setValue(''); // Make the status line visible and tell the user the possible actions app.getElementById('status').setVisible(true).setText('User ' + e.parameter.userName + ' entered.' + 'To add another, type in the information and click submit. To exit, click close.'); return app; }
doGet Parameters
The doGet
function has two parameters that are useful if you want to pass query string parameters to your web app's URL. The first parameter is queryString
, and its value will be a string containing the query string values that were appended to the URL. For example, its value may look something like this: a=1&b=2&c=3
. The second parameter is called parameter
, and it is an Object containing each parameter and value pair. Its value may look something like this:
{"a": "1", "b": "2", "c": 3}
Using client handlers for more responsive UIs
The click handlers discussed in the previous section are server-side handlers. They require a round trip from the user's browser to the server and back to the browser, so they may not be responsive enough for some situations. Client handlers enable your script to respond to any event in a browser without connecting to the server. What you can do in response to an event is limited to a set of predefined common actions, but you still have a lot of flexibility in making your app more responsive. In the previous example, the user may click the submit button several times causing the submit function to called several times. This becomes a more serious problem when the network connection is slow, and the user gets no immediate feedback for clicking the button.
To solve this problem, we can create handlers that are executed in the browser without calling the server. Using Client Handlers, your application can now respond to events in the browser without the need to perform a round trip to Google Apps Script servers. These client-side handlers are limited to a small set of actions for security reasons, but they solve many common problems, such as the one described above.
In a similar example, you may want to provide your users with instant feedback within your app. Imagine that your user has typed text where a number is expected. Ideally, you would want to warn users as they type the value, instead of waiting until the form is submitted. Having a server event handler for each keystroke is definitely overkill for such a simple and common task. These use cases are now supported with Google Apps Script's new client handlers and validators!
This simple application enables the user to click a button to display the “Hello world” message:
function doGet() { var app = UiApp.createApplication(); var button = app.createButton("Say Hello"); // Create a label with the "Hello World!" text and hide it for now var label = app.createLabel("Hello World!").setVisible(false); // Create a new handler that does not require the server. // We give the handler two actions to perform on different targets. // The first action disables the widget that invokes the handler // and the second displays the label. var handler = app.createClientHandler().forEventSource().setEnabled(false).forTargets( label).setVisible(true); // Add our new handler to be invoked when the button is clicked button.addClickHandler(handler); app.add(button); app.add(label); return app; }
The client handlers in the above example are setup in two steps.
- Create a client handler just as we would create a server handler
- Define the target widget for this handler. The target widget is the widget on which the handler will take action. We set the handler’s target in one of two ways: (a) By using the forTargets method to define the target widgets. (b) By using the forEventSource method which lets widgets wire themselves to the client handler.
In the above example, we set the handler’s target to be the event source, so that it will apply to the button that is clicked. Finally, we define the action that the handler should take, in this case disabling the button using setEnabled(false). Aside from setEnabled, you can also change styles using setStyleAttribute, change text using setText, and so on. One client handler can perform multiple actions - just chain them together - and you can even change the target so that some actions apply to one set of widgets and some actions to another set. In our example, along with disabling the button, we set the handler to display the label when it is invoked, using setVisible.
Using validators
Validators allow both client and server handlers to check simple conditions before they are invoked. For example, the following simple application adds two numbers given by the user, while using validators to make sure the server is only called if both of the text boxes contain numbers.
function doGet() { var app = UiApp.createApplication(); // Create input boxes and button var textBoxA = app.createTextBox().setId('textBoxA').setName('textBoxA'); var textBoxB = app.createTextBox().setId('textBoxB').setName('textBoxB'); var addButton = app.createButton("Add"); // Create a handler to call the adding function // Two validations are added to this handler so that it will // only invoke 'add' if both textBoxA and textBoxB contain // numbers var handler = app.createServerClickHandler('add').validateNumber(textBoxA).validateNumber( textBoxB).addCallbackElement(textBoxA).addCallbackElement(textBoxB); addButton.addClickHandler(handler); app.add(textBoxA); app.add(textBoxB); app.add(addButton); return app; } function add(e) { var app = UiApp.getActiveApplication(); var result = parseFloat(e.parameter.textBoxA) + parseFloat(e.parameter.textBoxB); var newResultLabel = app.createLabel("Result is: " + result); app.add(newResultLabel); return app; }
There’s a variety of validators to choose from and perform different tasks. You can verify the input to be a number, an integer, an e-mail address, of specific length or with a numerical value in a defined range. You can also use general regular expressions, and lastly each validator has its negation. Note that validators work with both client and server handlers.
Validators and Client handlers work best together. For example, in our addition example above, the “Add” button should be disabled as long as the current input is not numeric. We would also like to let the user know why the button is disabled by displaying an error message. To do so, we combine the power of server handlers, client handlers, and validators in the following way:
function doGet() { var app = UiApp.createApplication(); // Create input boxes and button. var textBoxA = app.createTextBox().setId('textBoxA').setName('textBoxA'); var textBoxB = app.createTextBox().setId('textBoxB').setName('textBoxB'); var addButton = app.createButton("Add").setEnabled(false); var label = app.createLabel("Please input two numbers"); // Create a handler to call the adding function. // Two validations are added to this handler so that it will // only invoke 'add' if both textBoxA and textBoxB contain // numbers. var handler = app.createServerClickHandler('add').validateNumber(textBoxA).validateNumber( textBoxB).addCallbackElement(textBoxA).addCallbackElement(textBoxB); // Create a handler to enable the button if all input is legal var onValidInput = app.createClientHandler().validateNumber(textBoxA).validateNumber(textBoxB).forTargets( addButton).setEnabled(true).forTargets(label).setVisible(false); // Create a handler to mark invalid input in textBoxA and disable the button var onInvalidInput1 = app.createClientHandler().validateNotNumber(textBoxA).forTargets(addButton).setEnabled( false).forTargets(textBoxA).setStyleAttribute("color", "red").forTargets( label).setVisible(true); // Create a handler to mark the input in textBoxA as valid var onValidInput1 = app.createClientHandler().validateNumber(textBoxA).forTargets(textBoxA).setStyleAttribute( "color", "black"); // Create a handler to mark invalid input in textBoxB and disable the button var onInvalidInput2 = app.createClientHandler().validateNotNumber(textBoxB).forTargets(addButton).setEnabled( false).forTargets(textBoxB).setStyleAttribute("color", "red").forTargets( label).setVisible(true); // Create a handler to mark the input in textBoxB as valid var onValidInput2 = app.createClientHandler().validateNumber(textBoxB).forTargets(textBoxB).setStyleAttribute( "color", "black"); // Add all the handlers to be called when the user types in the text boxes textBoxA.addKeyUpHandler(onInvalidInput1); textBoxB.addKeyUpHandler(onInvalidInput2); textBoxA.addKeyUpHandler(onValidInput1); textBoxB.addKeyUpHandler(onValidInput2); textBoxA.addKeyUpHandler(onValidInput); textBoxB.addKeyUpHandler(onValidInput); addButton.addClickHandler(handler); app.add(textBoxA); app.add(textBoxB); app.add(addButton); app.add(label); return app; } function add(e) { var app = UiApp.getActiveApplication(); var result = parseFloat(e.parameter.textBoxA) + parseFloat(e.parameter.textBoxB); var newResultLabel = app.createLabel("Result is: " + result); app.add(newResultLabel); return app; }
Using forms
As an alternative to using server handlers, there are some cases where you may want to use forms by creating a FormPanel
instead. A FormPanel
can be used with a SubmitButton
to post form values to the server. All children of this panel (direct, or even children of sub-panels) that have a setName
function and have been given a name will have their values sent to the server when the form is submitted. The submit can be handled in the special doPost
function, as shown in the example. Note that this panel can contain at most one direct child widget. To add more children, make the child of this panel a different panel that can contain more than one child. Here is an example of how to use this widget:
function doGet() { var app = UiApp.createApplication(); var form = app.createFormPanel(); var flow = app.createFlowPanel(); flow.add(app.createTextBox().setName("textBox")); flow.add(app.createListBox().setName("listBox").addItem("option 1").addItem("option 2")); flow.add(app.createSubmitButton("Submit")); form.add(flow); app.add(form); return app; } function doPost(eventInfo) { var app = UiApp.getActiveApplication(); app.add(app.createLabel("Form submitted. The text box's value was '" + eventInfo.parameter.textBox + "' and the list box's value was '" + eventInfo.parameter.listBox + "'")); return app; }
Deploying a user interface as a web app
Scripts that have user interfaces that are built using the UI service can be deployed as web apps. The process for deploying a script as a web app is described fully in the Web Apps document. In short, you will choose which version of your script you want to deploy, whether the script will execute as you (the script owner) or as the active user accessing the web app, and who can access the web app. Scripts that can be deployed as web apps contain a specially-named doGet
function, which is the function that is executed when a user visits the web app's URL. The doGet
function will always return an UiInstance
object.
Displaying a user interface from a spreadsheet
As an alternative to deploying your user interface as a standalone web app, you can create a container-bound script from a spreadsheet, and display the user interface from the spreadsheet. To do this, find your doGet
function and simply replace the call to
return app;with the following:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); spreadsheet.show(app);
where app
is the variable name for the UiInstance
object you are returning. Additionally, when you display the user interface from a spreadsheet, the function does not have to be named doGet
. You could instead call it something like displayMyUi
, and then call that function directly to display the user interface in your spreadsheet. When a user interface is displayed from a spreadsheet, the script runs as the user who's accessing the spreadsheet.
Using custom HTML
Using UiApp's HTML widget it's possible to add custom HTML markup to your application. There are restrictions on the type of content that can be added to this widget however, and only the following set of HTML tags can be used:
b, blockquote, body, br, center, caption, cite, code, div, em, h1, h2, h3, h4, h5, h6, hr,
i, label, legend, li, ol, p, span, strong, sub, sup, table, tbody, td, thead, title, tr, tt,
ul
To take advantage of the full set of HTML features, consider using the HTML service to build your user interface instead of UiApp.