Prevent server handler from firing - google-apps-script

Can we stop a server handler from firing if a client handler (on the same widget) meets a validation condition? UI
I don't want Submit to perform any server function if the text box is empty.
function doGet() {
var app = UiApp.createApplication();
var flex = app.createFlexTable()
.setWidget(0, 0, app.createTextBox().setId('textbox'))
.setWidget(0, 1, app.createButton('Submit').setId('submit'))
.setWidget(0, 2, app.createLabel().setId('status'));
var clientHandler = app.createClientHandler().validateNotMatches(app.getElementById('textbox'), ' ');
var serverHandler = app.createServerHandler('submit').addCallbackElement(flex);
app.getElementById('submit').addClickHandler(clientHandler).addClickHandler(serverHandler);
app.add(flex);
return app;
}
function submit(e) {
var app = UiApp.getActiveApplication();
app.getElementById('status').setText('Server handler fired');
return app;
}

You don't need or want a client handler here, just a validator on the server handler:
function doGet() {
var app = UiApp.createApplication();
var flex = app.createFlexTable()
.setWidget(0, 0, app.createTextBox().setId('textbox'))
.setWidget(0, 1, app.createButton('Submit').setId('submit'))
.setWidget(0, 2, app.createLabel().setId('status'));
var serverHandler = app.createServerHandler('submit')
.validateLength(app.getElementById('textbox'), 1, null)
.addCallbackElement(flex);
app.getElementById('submit').addClickHandler(serverHandler);
app.add(flex);
return app;
}
function submit(e) {
var app = UiApp.getActiveApplication();
app.getElementById('status').setText('Server handler fired');
return app;
}
If you want a message explaining what went wrong, you can add this:
var clientHandler = app.createClientHandler()
.validateNotLength(app.getElementById('textbox'), 1, null)
.forTargets(app.getElementById('status'))
.setText('Cannot be empty');
app.getElementById('submit').addClickHandler(serverHandler)
.addClickHandler(clientHandler);

Related

Custom Validation Function

I'm trying to understand how handlers work with validation in Google's UI Service.
If I have a text box with a button and I only want to contact the server if the text box is 1) Not Empty and 2) Contains a specific value, for e.g. 'Fred' how do I create a validation function that checks if the value is 'Fred' before allowing the serverhandler to fire?
Some example code:
function myValid() {
//create the app
var app = UiApp.createApplication();
//set out UI in table
var flex = app.createFlexTable()
.setWidget(0, 0, app.createTextBox().setName('textbox').setId('textbox'))
.setWidget(0, 1, app.createButton('Submit').setId('submit'))
.setWidget(0, 2, app.createLabel().setId('status'));
//server handler - fires only if textbox isn't empty
var serverHandler = app.createServerHandler('submit')
.validateLength(app.getElementById('textbox'), 1, null)
.addCallbackElement(flex);
//my custom handler to check the value of the textbox
var myserverHandler = app.createServerHandler('myCheck')
.addCallbackElement(flex);
//client handler to display a message if textbox is empty
var clientHandler = app.createClientHandler()
.validateNotLength(app.getElementById('textbox'), 1, null)
.forTargets(app.getElementById('status'))
.setText('Cannot be empty');
//add the handlers to the submit button
app.getElementById('submit')
.addClickHandler(serverHandler)
.addClickHandler(myserverHandler)
.addClickHandler(clientHandler);
//add table to UI
app.add(flex);
//show app in the current spreadsheet
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
//check to see if textbox value is 'Fred'. If not display error
function myCheck(e) {
var app = UiApp.getActiveApplication();
if (e.parameter.textbox != "Fred")
var errorLabel = app.createLabel("Incorrect Value");
app.add(errorLabel);
return app;
}
//If all validation passes then display message
function submit(e) {
var app = UiApp.getActiveApplication();
app.getElementById('status').setText('Server handler fired');
return app;
}
I have attempted to use a serverhandler to check the textbox value but a clienthandler would be better. How do I write a custom client handler to check the textbox value for 'Fred'? Also, how do I prevent the serverhandler firing before all my validation conditions are met?
Thanks
The solution is simpler than your code, one server handler is enough with a single validation on "Fred" and a ClientHandler using validateNotMatches.
Code below.
function myValid() {
var app = UiApp.createApplication();
var textBox = app.createTextBox().setName('textbox').setId('textbox');
var btn = app.createButton('Submit');
var label = app.createLabel().setId('status');
var flex = app.createFlexTable()
.setWidget(0, 0, textBox)
.setWidget(0, 1, btn)
.setWidget(0, 2, label);
var serverHandler = app.createServerHandler('submit')
.validateMatches(textBox, 'Fred')
.addCallbackElement(flex);
var clientHandler = app.createClientHandler()
.validateNotMatches(textBox, 'Fred').forTargets(label).setText('Missing or invalid value');
btn.addClickHandler(serverHandler).addClickHandler(clientHandler);
app.add(flex);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function submit(e) {
var app = UiApp.getActiveApplication();
app.getElementById('status').setText('Server handler fired');
return app;
}
This code accepts any answer "containing" Fred, if you want it to reject anything but "Fred" alone just add a validation on the textBox length == 4 (validateLength,textBox,4,4), I wasn't sure what you really wanted...
Edit : Thanks for accepting, this edit just to mention there is also another approach that might interest you using btn.setEnabled() and a KeyUpHandler, code below... the choice is yours ;-)
function myValid() {
var app = UiApp.createApplication();
var textBox = app.createTextBox().setName('textbox').setId('textbox');
var btn = app.createButton('Submit').setEnabled(false);
var label = app.createLabel().setId('status');
var flex = app.createFlexTable()
.setWidget(0, 0, textBox)
.setWidget(0, 1, btn)
.setWidget(0, 2, label);
var serverHandler = app.createServerHandler('submit')
.validateMatches(textBox, 'Fred')
.addCallbackElement(flex);
btn.addClickHandler(serverHandler);
var btnHandler = app.createClientHandler().validateMatches(textBox,'Fred').forTargets(btn).setEnabled(true);
textBox.addKeyUpHandler(btnHandler);
app.add(flex);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function submit(e) {
var app = UiApp.getActiveApplication();
app.getElementById('status').setText('Server handler fired');
return app;
}

Adding a ClientHandler like loading bar to a ServerClickHandler

I'm using the code below to run a a function that takes a while to load. My question is, without a lot of modification, is there an easy way of adding something like a clienthandler so that in the midst of running the function it says something along the lines of "Loading...?"
function doGet(e) {
var app = UiApp.createApplication().setTitle('New app');
var grid = app.createGrid(1, 2);
grid.setWidget(0, 0, app.createLabel('First Name + Last Name (Case Sensitive):'));
grid.setWidget(0, 1, app.createTextBox().setName('userName').setId('userName'));
var panel = app.createVerticalPanel();
panel.add(grid);
var buttonPanel = app.createHorizontalPanel();
var button = app.createButton('Submit');
var submitHandler = app.createServerClickHandler('submit')
submitHandler.addCallbackElement(grid);
button.addClickHandler(submitHandler);
buttonPanel.add(button);
var clearButton = app.createButton('Clear');
var clearHandler = app.createServerClickHandler('clear');
clearButton.addClickHandler(clearHandler);
buttonPanel.add(clearButton);
var statusLabel = app.createHTML("").setId('status').setVisible(false);
panel.add(statusLabel);
panel.add(buttonPanel);
app.add(panel);
return app;
};
function clear() {
var app = UiApp.getActiveApplication();
app.getElementById('userName').setValue('');
app.getElementById('status').setVisible(false)
return app;
};
Yes, indeed. That is what client handlers are for.
function doGet(e) {
var app = UiApp.createApplication().setTitle('New app');
var grid = app.createGrid(1, 2);
grid.setWidget(0, 0, app.createLabel('First Name + Last Name (Case Sensitive):'));
grid.setWidget(0, 1, app.createTextBox().setName('userName').setId('userName'));
/* Have the widget ready */
var statusLabel = app.createHTML("").setId('status').setVisible(false);
var panel = app.createVerticalPanel();
panel.add(grid);
var buttonPanel = app.createHorizontalPanel();
var button = app.createButton('Submit');
var submitHandler = app.createServerClickHandler('submit')
submitHandler.addCallbackElement(grid);
button.addClickHandler(submitHandler);
buttonPanel.add(button);
var clearButton = app.createButton('Clear');
var clearHandler = app.createServerClickHandler('clear');
/* Create the client handler */
var plswait = app.createClientHandler().forTargets(statusLabel).setText('Loading...');
clearButton.addClickHandler(clearHandler).addClickHandler(plswait);
buttonPanel.add(clearButton);
panel.add(statusLabel);
panel.add(buttonPanel);
app.add(panel);
return app;
};
function clear() {
var app = UiApp.getActiveApplication();
app.getElementById('userName').setValue('');
app.getElementById('status').setVisible(false).setText('');
return app;
};
It can be more pleasant if you add an image to your status label, here is an example using a transparent animated gif : (simply replace in Srik's code)
var statusLabel = ui.createHorizontalPanel().add(ui.createImage('https://dl.dropboxusercontent.com/u/211279/loading3T.gif'))
.add(ui.createHTML("Loading...")).setId('status').setVisible(false);// Label + animated gif, the text in the HTML widget can be there from the start.
Edit : just noticed something was missing in Srik's code... the clientHandler should go like this :
var plswait = app.createClientHandler().forTargets(statusLabel).setVisible(true);// and eventually setText(' blah blah...') if you didn't define/change it before/elsewhere
Note : now that you have a clientHandler you could also make use of it to setEnabled(false) the button that triggered it to avoid multiple clicks... simply add .forEventSource().setEnabled(false) to your clientHandler.

Not replacing e.parameter

I created this script to be able to send an email after a grid has been filled with information. Before the email is sent, another display comes in an warns the user to continue only if the information is correct. At the end the script sends an email to me, with the info entered by the user. The problem is here, when I received the email, the fields that the script is suppose to replace are shown as undefined and no info is there. Any ideas on what is wrong here??
Thank you!
function runmyapp() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var app = UiApp.createApplication().setTitle('Title 1');
var grid = app.createGrid(4, 5);
grid.setWidget(0, 0, app.createLabel('Time '));
grid.setWidget(0, 1, app.createTextBox().setName('Time'));
grid.setWidget(1, 0, app.createLabel('Minutes'));
grid.setWidget(1, 1, app.createTextBox().setName('Minutes'));
grid.setWidget(2, 0, app.createLabel('Enter Name'));
grid.setWidget(2, 1, app.createTextBox().setName('Name'));
grid.setWidget(3, 0, app.createLabel('Email'));
grid.setWidget(3, 1, app.createTextBox().setName('email'));
var panel = app.createVerticalPanel();
panel.add(grid);
var button = app.createButton('Submit').setId("button");
var handler2 = app.createServerHandler('dis');
handler2.addCallbackElement(grid);
button.addClickHandler(handler2);
var handler = app.createServerHandler('disc');
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);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function dis(e){
var app = UiApp.getActiveApplication();
app.getElementById("button").setText("Request is in process, please wait!").setEnabled(false);
return app;
};
function disc(e){
var app = UiApp.getActiveApplication();
var html1 = app.add(app.createHTML("<p><p>Hello Expert,</p>"+
"<p>By clicking OK you agree that your information is correct</p>");
var button = app.createButton('Ok').setId("button");
app.add(button);
var handler2 = app.createServerHandler('gsnot');
button.addClickHandler(handler2);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
return app;}
function gsnot(e) {
var advancedArgs = {bcc:e.parameter.email};
var emailSubject = "Subject";
var address ="albdominguez25#gmail.com";
var emailTemplate =SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A1").getValue( ) ;
emailTemplate = emailTemplate.replace("TIME", e.parameter.Times).replace("MIN", e.parameter.Minutes).replace("EXP", e.parameter.Name);
MailApp.sendEmail(address, emailSubject, emailTemplate, advancedArgs);
Browser.msgBox("Your Email has been sent!");
var app = UiApp.getActiveApplication();
app.close();
// The following line is REQUIRED for the widget to actually close.
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
To make a widget value show on your e.parameter you need to add it (or a parent panel) as a callback element on the handler. Like this:
function runmyapp() {
//...
var grid = app.createGrid(4, 5).setId('grid');
//...
}
function disc(e){
//...
var grid = app.getElementById('grid');
var handler2 = app.createServerHandler('gsnot').addCallbackElement(grid);
//...
}
Keep Henrique's answer as best one please.
You can try to change your disc(e) function like this :
function disc(e){
var app = UiApp.getActiveApplication();
var html1 = app.add(app.createHTML("<p><p>Hello Expert,</p>"+
"<p>By clicking OK you agree that your information is correct</p>"));
var grid = app.getElementById('grid')
var button = app.createButton('Ok').setId("button");
app.add(button);
var handler3 = app.createServerHandler('gsnot');// use a different name to avoid confusion
handler3.addCallbackElement(grid);
button.addClickHandler(handler3);
return app;}
And, also, there is a typo in your code in this line :
emailTemplate = emailTemplate.replace("TIME", e.parameter.Times).replace("MIN", e.parameter.Minutes).replace("EXP", e.parameter.Name);
Time has no 's' at the end in the original name ! ;)
and, last point, if I where you I wouldn't call show(app) in disc so the UI would keep the data validation while confirming... (but that's a matter of choice ;-)

Why does close() not do anything?

Does the app.close() not do anything? it does not appear to work....
// Script-as-app template.
Sample code
function doGet() {
var app = UiApp.createApplication();
var button = app.createButton('Click Me');
var closeButton = app.createButton('Close It');
app.add(closeButton);
app.add(button);
var label = app.createLabel('The button was clicked.')
.setId('statusLabel')
.setVisible(false);
var label2 = app.createLabel('The Close button was clicked.')
.setId('statusLabel2')
.setVisible(false);
app.add(label);
app.add(label2);
var closeHandler = app.createServerHandler('close');
closeHandler.addCallbackElement(label);
var handler = app.createServerHandler('myClickHandler');
handler.addCallbackElement(label);
closeButton.addClickHandler(closeHandler);
button.addClickHandler(handler);
return app;
}
function myClickHandler(e) {
var app = UiApp.getActiveApplication();
var label = app.getElementById('statusLabel');
label.setVisible(true);
app.close();
return app;
}
function close(e)
{
Logger.log("Here");
var app = UiApp.getActiveApplication();
var label2 = app.getElementById('statusLabel2');
label2.setVisible(true);
return app.close();
}
Link to sample code
https://script.google.com/a/macros/scotts.com/s/AKfycbz-avIT3bZNJ68_EpRZYXmh66XLJzYI--F0n7DGNn8jL_wlG1k/exec
As documented here:
"This will only work if the app is being shown as a dialog, such as in
a Google Spreadsheet. It will have no effect when called on an app
that is published as a standalone service."
That's a fundamental limit of browsers, by the way - a page can't close itself.

Is it possible to have an static variable in a handler?

I want to make an script that keeps track of the times a button is pressed. It is well known that javascript lacks of support for static vars but this can normally be workaround defining the variable outside of the target function.
Nevertheless this does not work for me on a simple google scripting web application. The code is the following one and it is a simple extension of the template web aplication.
Anyone knows how can this be achieved?
This is the code of the google application:
// Script-as-app template.
function doGet() {
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);
myClickHandler.counter = 0;
var handler = app.createServerHandler('myClickHandler');
handler.addCallbackElement(label);
button.addClickHandler(handler);
return app;
}
function myClickHandler(e) {
var app = UiApp.getActiveApplication();
var label = app.getElementById('statusLabel');
label.setVisible(true);
label.setText('Clicked ' + myClickHandler.counter + ' times.')
myClickHandler.counter++;
//app.close();
return app;
}
There are a few possible ways to achieve that, here is one of them using a hidden widget to hold values.
function doGet(){
var app = UiApp.createApplication();
var button = app.createButton('Click Me');
app.add(button);
var counterValue = 0;
var label = app.createLabel('The button was clicked.')
.setId('statusLabel')
.setVisible(false);
var counter = app.createHidden('counter').setId('counter').setValue(counterValue)
app.add(label).add(counter);
var handler = app.createServerHandler('myClickHandler');
handler.addCallbackElement(counter);
button.addClickHandler(handler);
return app;
}
function myClickHandler(e) {
var app = UiApp.getActiveApplication();
var label = app.getElementById('statusLabel');
label.setVisible(true);
var counterValue = Number(e.parameter.counter)
var counter = app.getElementById('counter')
counterValue++;
counter.setValue(counterValue.toString())
label.setText('Clicked ' + counterValue + ' times.')
return app;
}