Archiving Gmail (Part 3)

Time for a little aside.  In the last post I showed you were we were headed with a Google apps script web application that will archive or forward gmail messages in bulk.  Running this script can take a while if there are a lot of emails and Google apps scripts don't have a very good method for showing that they are running.

While I wanted a progress bar, I couldn't find anything that would easily get me there (although if you read Solution 2 below you could back your way into one but it would be cumbersome at best).

I settled on showing and hiding a spinning gif to indicate to the user the script is still running.  I found two ways to do this, but only one (the second) works with the menus I wanted to use.

Solution 1:

This solution does NOT work with menus.  Basically the idea is you add a normal server handler to a button (for example) then add an additional client handler to the MouseUp event. Click to see it in action.

Here is the simple code for that demo:

// Script-as-app template.
function doGet() {
  var app = UiApp.createApplication();

  var label = app.createLabel('click the button.')
                 .setId('statusLabel')
                 .setVisible(true);
  
  app.add(label);
  var spinner = app.createImage('https://5079980847011989849-a-1802744773732722657'+
            '-s-sites.googlegroups.com/site/scriptsexamples/ProgressSpinner.gif')
            .setVisible(false)
            .setId('spinner');
  app.add(spinner);

  
  var myButtonHandler=app.createServerHandler('myButtonLongRunningFunction');
  
  var myButton=app.createButton("Click to Test", myButtonHandler);
    var loadSpinner = app.createClientHandler()
            .forTargets(spinner)
            .setVisible(true).forTargets(label).setText('Running...');

  myButton.addMouseUpHandler(loadSpinner)
  app.add(myButton);
  return app;
  
}

function myButtonLongRunningFunction(e){

  Utilities.sleep(5000);
  
  var app=UiApp.getActiveApplication();
  app.getElementById('statusLabel').setText('Done.');
  app.getElementById('spinner').setVisible(false);
  return app;
}

Solution 2:

As I stated, the above method will NOT work if you use a menu.  Why? Because you can't assign both a client handler and server handler to a menuItem.   So how can we accomplish the same thing using only a server handler?  By tricking the script into running TWO server handlers asynchronously.  Click here to see this in action

Here are the basic steps:

  1. Create your menu
  2. Create an object like a textbox to hold the value of the menu item clicked
  3. Create a checkbox with a ValueChangeHandler.  this handler will check the textbox for the command that was requested, and perform some long running action.
  4. create a menu handler for each menuitem.  these menu handlers will show the progress indicator, set the value in the textbox, then toggle the checkbox value to trigger the ValueChangeHandler created above.

Here is the code for example two:

 

// Script-as-app template.
function doGet() {
  var app = UiApp.createApplication();

  //Create menu
      var menuBar = app.createMenuBar();//For horizontal menubar
    var handler1 = app.createServerHandler('myMenu1Handler');
    var handler2 = app.createServerHandler('myMenu2Handler');
  
  
    var menuItem0 = app.createMenuItem('Item 1', handler1);
    var menuItem1 = app.createMenuItem('Item 2', handler2);
      var separator0 = app.createMenuItemSeparator();
    var separator1 = app.createMenuItemSeparator();
    menuBar.addSeparator(separator0).addItem(menuItem0).addSeparator(separator1)
    .addItem(menuItem1);
  app.add(menuBar);
  
  //Create the control checkbox trigger and textbox
  var myControlTextBox=app.createTextBox().setId('controlTextBox').setName('controlTextBox').setVisible(false);  
  var controlHandler=app.createServerHandler('myControlHandler');
  
  var myControlCheckBox=app.createCheckBox().setId('controlCheckBox').setName('controlCheckBox').addValueChangeHandler(controlHandler).setVisible(false);

  controlHandler.addCallbackElement(myControlTextBox);
  controlHandler.addCallbackElement(myControlCheckBox);

  app.add(myControlTextBox);
  app.add(myControlCheckBox);

  //create the spinner we will use for progress
    var spinner = app.createImage('https://5079980847011989849-a-1802744773732722657'+
            '-s-sites.googlegroups.com/site/scriptsexamples/ProgressSpinner.gif')
            .setVisible(false)
            .setId('spinner');
  app.add(spinner);
  var resultsLabel=app.createLabel().setVisible(false).setId('resultsLabel');
  app.add(resultsLabel);
  return app;
}

function myControlHandler(e){
  Logger.log("control called");
  var app=UiApp.getActiveApplication();
  if(e.parameter.controlCheckBox=='true'){
    if(e.parameter.controlTextBox=='Menu1'){
      
      Utilities.sleep(3000);
      
      app.getElementById('spinner').setVisible(false);
      app.getElementById('resultsLabel').setText('Menu 1 was clicked').setVisible(true);
      //reset control box
      var controlCheck=app.getElementById('controlCheckBox');
      controlCheck.setValue(false,false);
    } else if(e.parameter.controlTextBox=='Menu2'){
      Utilities.sleep(3000);
      app.getElementById('spinner').setVisible(false);
      app.getElementById('resultsLabel').setText('Menu 2 was clicked').setVisible(true);
      //reset control box
      var controlCheck=app.getElementById('controlCheckBox');
      controlCheck.setValue(false,false);
    } else {
      Logger.log("controlTextBox value: " + e.parameter.controlTextBox);
    }
  } else {
    //Do nothing - this is result of reset from above.
  }
  return app;
}
function myMenu1Handler(e) {
  var app = UiApp.getActiveApplication();
  //show the spinner
  app.getElementById('spinner').setVisible(true);
  app.getElementById('controlTextBox').setText('Menu1');
  app.getElementById('resultsLabel').setVisible(false);
  var controlCheck=app.getElementById('controlCheckBox');
  controlCheck.setValue(true,true);
  return app;
}
function myMenu2Handler(e) {
  var app = UiApp.getActiveApplication();
  //show the spinner
  app.getElementById('spinner').setVisible(true);
  app.getElementById('controlTextBox').setText('Menu2');
  app.getElementById('resultsLabel').setVisible(false);
   var controlCheck=app.getElementById('controlCheckBox');
  controlCheck.setValue(true,true);
  return app;
}

Since I decided that I liked the look of the menu better, I went with option 2.

to be continued....