GAS UI - Menus and Side Bars

UI stands for user interface. It can refer to this in regards to function and also design. I'm not much of a designer so I am going to focus mainly on the functional side. If you want to read more about UI (and the difference between UI and UX), this article was helpful for me.

In GAS, developers have the ability to add UI function and design to the Google app's native UI. Therefore, when users interact with your app or add-on, they may not realize when they are interacting with the native UI or your UI.

Custom Menus

I've covered menus here so I'll try not to be too redundant.

One thing I want to add is that if you are developing and add-on, you can use .createAddonMenu("Your Menu") instead of .createMenu(). Also, if you are developing an add-on, be sure to read through the documentation on this, especially if you will need authorization for your scripts to run. These two menu methods act differently based on whether or not authorization is required so if you are not getting the desired result, this may be why.

//Creates a custom menu with submenu items upon opening the spreadsheet

function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu('Custom Menu')

    //First menu item ('Name of button', 'functionToRun')
    .addItem('Run', 'createNewSheet')

    //Adds a horizontal line to the menu (optional)
    .addSeparator()

    //If you want to add sub-menu (optional):     
    .addSubMenu(ui.createMenu('Sub-menu')
      .addItem('Second item', 'menuItem2'))
    .addSeparator()

    //This is the menu button for the rest of the script. More on that later. (Second item optional)
    .addItem('Sync', 'logUserEvents')
    .addSeparator()
    .addItem('Show sidebar', 'showSidebar')
    .addToUi();
}

Custom Buttons

One UI feature that does not resemble native UI is the ability to turn an image into a button that runs a function when clicked. This comes in handy when your script needs to be run from a mobile device. Since this is well documented by Google, I'm going to just link to it.

Side Bars

The basics of the GAS side bar are pretty straightforward and can be found here. One bit of clarification, much of the UI cannot be triggered by clicking run in the GAS editor. They must be run on the client-side, meaning they must be run from within the app. This makes it difficult to test when you have a side bar that is multiple pages long or otherwise takes several steps to get to.

One way around this is to create function shortcut within the app. You can do this by creating a custom button with the respective function as the assigned script or to add it as a menu item. I like to use the menu option and create a test menu item. Then I assign that menu item to the function that runs the UI element I want to test.

Select list from items in a sheet

In several of my projects, I have populated a sheet with some user info (e.g. list of user$apos;s calendars). In the side bar, I use a drop down (i.e. <select> tag) with the user info. When the user clicks submit, the info from the drop down then gets sent back to the server to be used in a function. In order to accomplish this, the html file needs to run a script to retrieve the info from the sheet.

Essentially, functions called from the client-side must be done like this: google.script.run.function(). The first function that runs is .calendarNameDropDown() (server-side) which returns an array of the names of all the user's calendars. Then, upon successful execution of that function (hence .withSuccessHandler()) another function is run (client-side) that creates a text list containing each calendar name and adds each name as an option to the <select> tag with the id calendar_name.

The next section is the function that is run when the user clicks next (i.e. submit). Because I want to do something other than submit the form when the user clicks next, I use .preventDefault() on the event listener. Otherwise, I would not be able to run any other functions when the user clicks next. When clicked, the selection from the drop down is sent back to the server via .calendarName(this).

<script>

  (function () {

    //If .calendarNameDropDown() is successful
    google.script.run.withSuccessHandler(

      function (selectList) {
        var select = document.getElementById("calendar_name");

        for( var i=0; i<selectList.length; i++ ) {
          var option = document.createElement("option");
          option.text = selectList[i][0];
          select.add(option);
        }
      }

    //Gets list of user's calendars 
    ).calendarNameDropDown();
  }());

  document.querySelector("#myform").addEventListener("submit",
    function(e) {

      //Prevents form submission so other functions can run
      e.preventDefault();

      //Sends user selection back to the server
      google.script.run.calendarName(this);

      //To next side bar page
      google.script.run.toPage2();
    }
  );
</script>

Sidebar Loader

Adding a loader to your side bar can give it a really nice touch for those scripts that take a little longer to execute. I have found that without some sort of loading screen, the user may not be able to tell that the script is running. The html file below shows an example of a loading page that I have used in several projects.

While reading through some forums about this, I noticed that some users had a difficult time figuring out how to close the loading page once the script execution was complete. Because GAS is asynchronous, what seems to work for me is to call the function of the loading page and the long function upon submission of the previous page and call the function of the next page in my side bar at the end of the long function. So it looks like this: user clicks submit which calls longFunction() and loadingPage(). Once complete, longFunction() calls nextPage().

Please wait...

<!DOCTYPE html>

<html>
  <head>
    <base target="_top">

    <style>
      .loader {
        border: 16px solid #f3f3f3; /* Light grey */
        border-top: 16px solid #3498db; /* Blue */
        border-radius: 50%;
        width: 120px;
        height: 120px;
        animation: spin 2s linear infinite;
        margin: auto;
      }

      @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
      }
    </style>
  </head>

  <body>
    <h3 style="text-align: center">Please wait...</h3>

    <div class="loader"></div>
  </body>
</html>