Thursday, March 12, 2015
Creating More Responsive Applications with Client Handlers and Validators
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:
- Create a Client Handler just as we would create the server handlers you all know and love.
- 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 theforEventSource
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.
If youd like to chat about these new features or have other questions about Google Apps Script, please join several members of the Apps Script team in the Google Apps Developer Office Hours on Google+ Hangouts tomorrow, Wednesday November 16th at 10am PST. You can also ask questions at any time in the Apps Script forum.
Omer Strulovich profile Omer was an intern on the Google Docs team for the summer of 2011. He is now back to pursuing his master’s degree in the field of cryptography. |
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.