Google Apps Script - Share widgets between functions - google-apps-script

This is my first time working with Google apps scripts, and I'm a bit confused as to how to access widgets from multiple functions.
Basically, I'd like to have a button that updates a label widget. So the label has some default text, but then updates to show some other text after an 'Update' button is pressed.
From what I've read, the only things that can be passed into event handlers are objects with a setName method. A label widget doesn't have this, so what can I do to update the value of a widget in my doGet function from the other handler function?
Here is an idea of what I'd like to do (but can't get to work):
function doGet() {
var app = UiApp.createApplication();
// Create the label
var myLabel = app.createLabel('this is my label')
app.add(myLabel)
// Create the update button
var updateButton = app.createButton('Update Label');
app.add(updateButton)
// Assign the update button handler
var updateButtonHandler = app.createServerHandler('updateValues');
updateButton.addClickHandler(updateButtonHandler);
return app;
}
function updateValues() {
var app = UiApp.getActiveApplication();
// Update the label
app.myLabel.setLabel('This is my updated label')
return app;
}
I've been scouring the internet for hours trying to find a solution but can't seem to figure it out. Any suggestions?

What you mention about getting the value of a widget from an object name property is to GET the value of a widget, not to SET it. (in this case uppercase is not to "shout" but simply to get attention :-))
And the example of the Label is typically an example of a widget that you cannot read the value...
What you are looking for is a way to set widget value : you have to get the element by its ID : see example below in your updated code:
function doGet() {
var app = UiApp.createApplication();
// Create the label
var myLabel = app.createLabel('this is my label').setId('label');
app.add(myLabel)
// Create the update button
var updateButton = app.createButton('Update Label');
app.add(updateButton)
// Assign the update button handler
var updateButtonHandler = app.createServerHandler('updateValues');
updateButton.addClickHandler(updateButtonHandler);
return app;
}
function updateValues() {
var app = UiApp.getActiveApplication();
// Update the label
var label = app.getElementById('label').setText('This is my updated label');
return app;
}

Related

Automatically close a Google Apps Script UiApp after five seconds

I want to automatically close this UiApp after a certain number of seconds:
function showConfirmationDialogue() {
var app = UiApp.createApplication().setHeight('80').setWidth('400');
app.setTitle('test');
var panel = app.createVerticalPanel();
app.add(panel);
var doc = SpreadsheetApp.getActive();
doc.show(app);
// this part doesn't seem to work
Utilities.sleep(5000);
app.close();
return app;
}
Thanks!
The Ui you create is shown when you call doc.show(app) and the only way you can update it or close it is to use a handler function that ends with a return app.
So it is not possible to do what you want from the same function that creates the UI since it is "returned" only one time.
I know only one trick that can achieve what you want that is using a handler trigger source that will call a closing handler function automatically using a "special" property of the checkBox widget. Here is the code, it uses a checkBox that you can of course make invisible in your final code.
function showConfirmationDialogue() {
var app = UiApp.createApplication().setHeight('80').setWidth('400');
app.setTitle('test');
var panel = app.createVerticalPanel();
app.add(panel);
var handler = app.createServerHandler('closeWindow');
var chk = app.createCheckBox('checkBox to set invisible in real function').setValue(false,true).addValueChangeHandler(handler);
app.add(chk);
chk.setValue(true,true)//.setVisible(false);
var doc = SpreadsheetApp.getActive();
doc.show(app);
}
function closeWindow(){
Utilities.sleep(5000);
var app = UiApp.getActiveApplication().close();
return app;
}
You can use the same procedure to modify the UiApp instance in any way, change a Label text, add a widget... anything you want.

Can't retrieve ServerHandler by calling app.getElementById(id) function

I am trying to build a FlexTable with dynamic number of rows. Each row contains a TextBox. There is a button to add new row to the table. And a Submit button that should send all TextBox widgets for processing.
I have initially declared a ServerHandler and added to it all fixed elements:
var handler = app.createServerHandler('handler12')
.addCallbackElement(fixedTextBox1)
.addCallbackElement(fixedTextBox2);
handler.setId('myHandlerId');
button.addClickHandler(handler);
Note that I have set an id to the handler. Now when I process the addition of new TextBox (row to the table) I have to call addCallbackElement(newlyCreatedField) to the handler. Otherwise I will not have it as param when the form is submitted.
I do this:
var handler = app.getElementById('myHandlerId');
handler.addCallbackElement(newlyCreatedField);
But I get the following error:
Error encountered: Cannot find function addCallbackElement in object Generic.
I logged the type of the handler var with Logger.log(handler.getType()) and it says Generic. I called app.getElementById(id) on non-existent id and it again returns an object of type Generic.
What am I doing wrong? Thank you for looking into this. Thank you for your time.
Is there a specific reason why you want to add each text box as a callback element. If not I suggest that you add the Flextable as the callback element and all the elements within the Flextable will be recognized in your handler functions.
var handler = app.createServerHandler('handler12')
.addCallbackElement(flexTable);
(Srik beat me to it...) You don't need to have every TextBox as a CallbackElement, if you have the container as a CallbackElement.
You will need to keep the names of the contained TextBox elements unique, and keep track of those names. To do that, you can use a Tag, and update it as you go.
function doGet() {
var app = UiApp.createApplication();
// FlexTable - use tag to count # rows.
var fTable = app.createFlexTable()
.setId('fTable')
.setTag('0');
app.add(fTable);
// Button to add new row
var addRow = app.createButton('Add Row');
app.add(addRow);
var addRowHandler = app.createServerHandler('addRowHandler');
addRowHandler.addCallbackElement(fTable);
addRow.addClickHandler(addRowHandler);
// Submit button
var submit = app.createButton('Submit');
app.add(submit);
var submitHandler = app.createServerHandler('submitHandler');
submitHandler.addCallbackElement(fTable);
submit.addClickHandler(submitHandler);
return app;
}
function addRowHandler(e) {
var app = UiApp.getActiveApplication();
var row = parseInt( e.parameter.fTable_tag ); // Get row counter
var tBox = 'textBox'+row;
var textBox = app.createTextBox()
.setId(tBox)
.setName(tBox);
// Place new text box in FlexTable
var fTable = app.getElementById('fTable');
fTable.setWidget(row, 0, textBox);
// Update row counter
fTable.setTag((++row).toString());
return app;
}
function submitHandler(e) {
var app = UiApp.getActiveApplication();
var rows = parseInt( e.parameter.fTable_tag ); // Get row counter
// Log contents of all textBoxes in FlexTable
for (var row=0; row<rows; row++) {
var tBox = 'textBox'+row;
Logger.log(tBox+'='+e.parameter[tBox]);
}
return app;
}
A more complete example of using dynamic form elements is available on Waqar's Apps Script Tutorial.

Call function after UiApp is loaded (Javascript onLoad equivalent)

I have a UiApp form that requires some spreadsheet data (slowish retrieval). What I'd like to achieve is load the view as usual and after it has been displayed do something like calling a server handler that gets the data and updates my view when it's done. Is that possible?
I looked around but I didn't find anything.
According to this, "function doGet(e) is the GWT equivalent of onLoad", but it's not in my case as I would like to dispay the page and only then do the slow part...
Stumbled on this which we could then use to implement a pseudo-onLoad event.
The setValue function on checkboxes has an optional parameter to fire the valueChanged handler. So you can programmatically trigger the valueChanged handler, which allows you to return the app (load it initially) while the handler is being invoked simultaneously.
function doGet() {
var app = UiApp.createApplication();
var label = app.createLabel('Loading the data from the spreadsheet.')
.setId('statusLabel')
app.add(label);
var handler = app.createServerHandler('loadData');
var chk = app.createCheckBox('test').addValueChangeHandler(handler).setVisible(false).setValue(true,true).setId('chk');
app.add(chk);
return app;
}
function loadData(e) {
var app = UiApp.getActiveApplication();
Utilities.sleep(3000); //placeholder for some function that takes a long time.
var label = app.getElementById('statusLabel');
label.setText("Loaded the data from the spreadsheet");
return app;
}
Several other widgets have setValue(value, fireEvents) methods, eg. TextBox, PasswordTextBox etc. It seems like there is nothing unique about the CheckBox widget in implementing this technique.

How do events work in Google Script in regards to getting data from elements

Question 1) When a button is clicked is it possible to use something like this (see code below)?
function Submit(e) {
var app = UiApp.getActiveApplication();
var checked = app.getElementById("checkbox").getValue();
}
Question 2) When a label is clicked is it possible to use something like this (see code below)?
function LabelClick(e) {
var LabelText = e.parameter.getText();
}
Sorry, this probably stupid, but I can't see to find any decent examples of this and can't seem to work this out from Google's documentation and I'm just getting used to google script too. If you have the answer I would really appreciate it.
you are not very far... but not close enough to get it working...
Ui element's value is sent to the handler function in a so called callbackelement that is added to the handler. This callbackelement may be a button, a label or, more easily, the parent widget that contains all the other widgets. These "elements" are in the "e" of the handler function and are identified by their names.
In the other direction, ie if you need to modify an Ui element from another function then you can get this element by its ID (getElementbyId()) and assign it a value just the same way as you'd do it in the UI definition function.
I copy/paste a sample code from another post to illustrate what I said, you can see the e.parameter.chkmode that holds the value of the checkBox and I'll add a Label to show the reverse process (the text is changed when the button is clicked).
Hoping I was clear enough,
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
//
function move() {
var app = UiApp.createApplication().setTitle("move test")
.setHeight(100).setWidth(400).setStyleAttribute("background-color","beige");
var panel = app.createVerticalPanel();
var next = app.createButton('next').setWidth('180');
var chkmode = app.createCheckBox("moving mode (checked = up/dwn, unchecked=L/R)").setValue(false).setName('chkmode');
var label = app.createLabel("test Label with text that will be modified on click").setId('label');
panel.add(next).add(chkmode).add(label);
var handler = app.createServerHandler('click').addCallbackElement(panel);
next.addClickHandler(handler);
app.add(panel);
ss.show(app);
}
//
function click(e) {
var app = UiApp.getActiveApplication();
var activeline = sh.getActiveRange().getRow();// get the row number of the selected cell/range
var activecol = sh.getActiveRange().getColumn();// get the row number of the selected cell/range
var label = app.getElementById('label');
label.setText('You have clicked the button');
var chkmode=e.parameter.chkmode;
if(chkmode=="true"){
activeline++
}else{
activecol++}
var sel=sh.getRange(activeline,activecol);
sh.setActiveSelection(sel);// make the next row active
return app;
}
For a UI and events tutorial, I recommend:
https://developers.google.com/apps-script/uiapp

Unable to use list box with server handler to change text of label

If the title is confusing, hopefully this makes it more clear what I'm trying to do:
function doGet(e) {
var app = UiApp.createApplication();
var list = app.createListBox().setName('list');
list.addItem("this");
list.addItem("that");
list.addItem("they");
var handler = app.createServerHandler('foo').addCallbackElement(list);
list.addChangeHandler(handler);
var label = app.createLabel("test").setId('label');
var panel = app.createVerticalPanel();
panel.add(list);
panel.add(label);
app.add(panel);
return app;
}​
function foo(e) {
var app = UiApp.getActiveApplication();
var value = e.parameter.list;
var label = app.getElementById('label');
label.setText(value);
}
It doesn't call any errors. If I intentionally put an error in foo, I get an error message, so I'm assuming the handler is getting called and just isn't doing anything. This works in a spreadsheet if I just have it bring up a Browser.msgBox(value), so I know that much works.
I'm trying to use this in a program that will automatically update all the listboxes on the page based on what is selected in the first listbox. I've been able to change things like visibility using server handlers and app.getElementById, but only with radio buttons, not a list box. I'm clearly doing something wrong here, but it's not obvious what that is.
To have your changes to the UiApp updated you have to return the app on your handler, e.g.
//...
label.setText(value);
return app;
}