Sunday, January 22, 2012

Creating Applications with Client Handlers

  When it comes to writing UI applications in Apps Script, we get a lot of requests to support event callbacks that are handled in the user’s browser. For example, if your application has a form, you may want to disable a button after it is clicked the first time. Until now, the only way to do that would be by using an event handler on the server to disable that button. 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. By cutting out the round trip to the server, your app can respond instantly to user input. Imagine, for example, you want to provide your users with instant feedback within your app when a user types 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. Luckily, these use cases are now supported with Apps Script’s new Client Handlers and validators! Let’s take a look at some code.

Client Handlers

A Client Handler allows you to react 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 have a lot of flexibility in making your app more responsive. You can use Client Handlers in any UiApp regardless of whether you are embedding in a Spreadsheet or a Sites Page or publishing as a service. This simple application enables the user to click a button to display the classic “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 set up in two steps:
  1. Create a Client Handler just as we would create the server handlers you all know and love.
  2. 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 widget. (b) By using the forEventSource method which lets widget wire itself 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.

Validators

Another new addition to Apps Script is support for validators in handlers. Validators allow handlers to check simple and complex conditions before they are invoked. For example, the following 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 that perform different tasks. You can verify the input to be a number, an integer, or an e-mail address. You can check for a specific length, or for any numerical value in a defined range. You can also use general regular expressions. Lastly, each validator has its negation. Note that validators work with both client and server handlers.

Putting it all together

Of course, validators and Client Handlers work best together. For example, in our addition application 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 handler to enable the button well all input is legal
  var onValidInput = app.createClientHandler()
      .validateNumber(textBoxA)
      .validateNumber(textBoxB)
      .forTargets(addButton).setEnabled(true)
      .forTargets(label).setVisible(false);

  // Create 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 handler to mark the input in textBoxA as valid
  var onValidInput1 = app.createClientHandler()
      .validateNumber(textBoxA)
      .forTargets(textBoxA).setStyleAttribute("color", "black");

  // Create 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 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;
}
All of these features can be used to create more advanced and responsive applications. Client handlers can be used to change several attributes for widgets, and validators can help you check a variety of different conditions from well formed email addresses to general regular expressions.

No comments:

Post a Comment

Share This: