I would like to change the callback function within another server handler. I get a response "Cannot find function setCallbackFunction in object Generic." as a result of
app.getElementById('treeHandler').setCallbackFunction('noSelection');
while the handler is defined in the mainline as
var handler = app.createServerHandler('nameSelected').setId('treeHandler');
so it looks as though we can't get elements of type ServerHandler within a server handler.
Is this expected behaviour?
That is correct; you cannot get them back and do anything with them.
That is not completely correct, you can add an hidden element to application. Just adopt the axmaple below by using createHidden instead of createTextBox.
function doGet() {
var app = UiApp.createApplication();
var textBox1 = app.createTextBox().setName("textBox1");
var textBox2 = app.createTextBox().setId("textBox2");
app.add(textBox1);
app.add(textBox2);
var textBox3 = app.createTextBox().setName("textBox3");
var panel = app.createFlowPanel();
panel.add(textBox3);
app.add(panel);
var button = app.createButton("a button");
var handler = app.createServerHandler("handlerFunction");
handler.addCallbackElement(textBox1)
.addCallbackElement(textBox2)
.addCallbackElement(panel)
button.addClickHandler(handler);
app.add(button);
return app;
}
function handlerFunction(eventInfo) {
var parameter = eventInfo.parameter;
// There's a lot of information in 'parameter' about the event too, but we'll focus here
// only on the callback elements.
var textBox1 = parameter.textBox1; // the value of textBox1
var textBox2 = parameter.textBox2; // undefined! setId is not the same as setName
var textBox3 = parameter.textBox3; // works! the parent "panel" was a callback element
}
Source: https://developers.google.com/apps-script/class_serverhandler#addCallbackElement
Related
I need global variable in google script, to hold page ID, like a string. Here, they suggested to use object Hidden for this purpose. I can create this object and set its value.
Code to achieve the same :
function doGet(e) {
var app = UiApp.createApplication();
//Get current indentificator
var mid = 'page-id';
app.add( app.createHidden('mid').setValue(mid).setId('mid'));
return app;
}
But how can I get this value from another function?
For example :
function maketbl(){
var app = UiApp.getActiveApplication();
app.(?!)
}
Thanks!
I see that your requirement is to have functionality similar to that of global variables. I suggest that you use script properties, user properties or the cache service to accomplish this feat. An example is below
ScriptProperties.setProperty('special', 'sauce'); // Use this to set the property
var specialValue = ScriptProperties.getProperty('special'); // use this to access the property
Try the bellow code:
function doGet(e) {
var app = UiApp.createApplication();
var mid = 'page-id';
var hidden = app.createHidden('mid').setValue(mid).setId('mid');
app.add(hidden);
//Create your handler
var handler = app.createServerHandler('maketbl');
handler.addCallbackElement(hidden)
//Create a button to trigger your function
app.add(app.createButton().setText("go forest").addClickHandler(handler));
return app;
}
function maketbl(e){
var app = UiApp.getActiveApplication();
//Retrieve the hidden field
var hidden = app.getElementById("mid");
//Show the value stored at e.parameter.mid where mid is the name of the field
var dialogBox = app.createDialogBox().setText(e.parameter.mid);
dialogBox.setPopupPosition(100, 100);
dialogBox.show();
return app;
}
Live version here.
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.
I have been playing with this small test code that - I admit - isn't very useful but I noticed that the value returned in the callBackElement of the first handler is undefined when this handler is called for the first time.
I couldn't figure out why... so I added a condition that solves the problem but I still would like to understand why this is working like that...
The script comes from a idea shown in this post earlier today, I commented the line that causes the error in the script below (it's a bit long, sorry about that) and runs as a sort of timer/counter to illustrate the ability to fire a handler programmatically with checkBoxes.
If someone can explain why this condition is necessary ?
var nn=0;
//
function doGet() {
var app = UiApp.createApplication().setHeight('120').setWidth('200').setTitle('Timer/counter test');
var Panel = app.createVerticalPanel()
var label = app.createLabel('Initial display')
.setId('statusLabel')
app.add(label);
var counter = app.createTextBox().setName('counter').setId('counter').setValue('0')
var handler1 = app.createServerHandler('loadData1').addCallbackElement(Panel);
var handler2 = app.createServerHandler('loadData2').addCallbackElement(Panel);
var chk1 = app.createCheckBox('test1').addValueChangeHandler(handler1).setVisible(true).setValue(true,true).setId('chk1');
var chk2 = app.createCheckBox('test2').addValueChangeHandler(handler2).setVisible(true).setValue(false,false).setId('chk2');
app.add(Panel.add(chk1).add(chk2).add(counter));
SpreadsheetApp.getActive().show(app)
}
function loadData1(e) {
var app = UiApp.getActiveApplication();
var xx = e.parameter.counter
//*******************************************************
if(xx){nn = Number(xx)}; // here is the question
// nn = Number(xx); // if I use this line the first occurence = undefined
nn++
var cnt = app.getElementById('counter').setValue(nn)
Utilities.sleep(500);
var chk1 = app.getElementById('chk1').setValue(false,false)
var chk2 = app.getElementById('chk2').setValue(true,true)
var label = app.getElementById('statusLabel');
label.setText("Handler 1 :-(");
return app;
}
function loadData2(e) {
var app = UiApp.getActiveApplication();
var xx = Number(e.parameter.counter)
xx++
var cnt = app.getElementById('counter').setValue(xx)
Utilities.sleep(500);
var chk1 = app.getElementById('chk1').setValue(true,true)
var chk2 = app.getElementById('chk2').setValue(false,false)
var label = app.getElementById('statusLabel');
label.setText("Handler 2 ;-)");
return app;
}
The app looks like this:
and is testable here
EDIT : working solution is to fire the handler after adding the widgets to the panel (see Phil's answer)
like this :
var chk1 = app.createCheckBox('test1').addValueChangeHandler(handler1).setVisible(true).setId('chk1');
var chk2 = app.createCheckBox('test2').addValueChangeHandler(handler2).setVisible(true).setId('chk2');
app.add(Panel.add(chk1).add(chk2).add(counter));
chk1.setValue(true,true);
chk2.setValue(false,false);
return app
The callback element which you specified for that handler (Panel) has no elements at the time that it is invoked. So you are essentially passing along a empty panel to that handler. So since chk1 hasn't been added to the panel yet, its value isn't added as a parameter to the handler.
Put chk1.setValue(true,true) after the call to Panel.add(chk1).
As seen in this example, the handler is queued when setValue(true,true) is called. This means that all the parameters that will be passed to the handler are gathered. It looks at the callback elements, reads their values, and then continues executing the doGet. After doGet finishes, the handler is executed.
On the code below I am defining a TextBox with name and id. The button handler works fine but I am not been able to get the value entered on the TextBox. The msgBox shows up but the e.parameter.matchValue shows as undefined.
On other part of the app I have the same logic but with a ListBox and it works fine.
What am I doing wrong?
function chooseColumnValueToMatch() {
var app = UiApp.createApplication().setHeight(150).setWidth(250);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var columnName = ScriptProperties.getProperty("columnNameToMatch");
var h1Line = app.createHTML("<h2>Value to match "+columnName+":</h2>");
var textPara = app.createHTML("<p>Type the VALUE that the <b>"+columnName+"</b> column must match EXACTLY to send the message.</p>");
var selectionHandler = app.createServerHandler('chooseColumnValueToMatchSelectionHandler');
var valueInputBox = app.createTextBox()
.setTitle("Match value for "+columnName)
.setName("matchValue")
.setId("matchValue");
var submitBtn = app.createButton("Submit", selectionHandler);
app.add(h1Line)
.add(textPara)
.add(valueInputBox)
.add(submitBtn);
ss.show(app);
}
function chooseColumnValueToMatchSelectionHandler(e){
var app = UiApp.getActiveApplication();
var columnName = ScriptProperties.getProperty("columnNameToMatch");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var msg = e.parameter.matchValue;
Browser.msgBox("Value to match "+columnName+" column is:", msg, Browser.Buttons.OK);
return app;
}
You have to use the ServerHandler.addCallbackElement method. The following code demonstrates it. The method call "tells" GAS internals that the value of the widget pointed as the parameter should be passed to the handler.
If you have multiple widgets, which should be handled by the handler, then, instead of calling the addCallbackElement with every widget, is possible to place all controls to a panel and point only the panel as the addCallbackElement method parameter. The code in the point 4.4 of this tutorial shows this way.
function doGet(e) {
var app = UiApp.createApplication();
var valueInputBox = app.createTextBox()
.setTitle("Match value for XXX")
.setName("matchValue")
.setId("matchValue");
var selectionHandler = app.createServerHandler('chooseColumnValueToMatchSelectionHandler');
selectionHandler.addCallbackElement(valueInputBox);
var submitBtn = app.createButton("Submit", selectionHandler);
var output = app.createLabel().setId("output");
app.add(valueInputBox).add(submitBtn).add(output);
return app;
}
function chooseColumnValueToMatchSelectionHandler(e){
var app = UiApp.getActiveApplication();
var output = app.getElementById("output");
var msg = e.parameter.matchValue;
output.setText("Value to match XXXX column is: " + msg);
return app;
}
It seems that you forgot to add a callbackElement to your handler
you could try like this :
selectionHandler.addCallbackElement(valueInputBox)
right after the submitBtn definition
I tried this small code to test setTag() and getTag() (on a vertical panel) in UI Service. I don't know why it doesn't work... any suggestion ?
online test
function doGet() {
var app = UiApp.createApplication();
var panel = app.createFlowPanel().setId('panel').setTag('start empty');// sets an initial value
var txt1 = app.createTextBox().setName('txt1').setId('txt1');
var txt2 = app.createTextBox().setName('txt2').setId('txt2');
var label = app.createLabel('type in TextBox 1 and then click button');
var button = app.createButton('click to trigger clienthandler');
app.add(panel.add(label).add(txt1).add(txt2).add(button));
var serverHandler = app.createServerHandler("key").addCallbackElement(panel);// this handler synchronizes the tag with textBox1
txt1.addKeyUpHandler(serverHandler);
var clientHandler = app.createClientHandler()
.forTargets(txt2).setText(panel.getTag()).setStyleAttribute('background','red');;// this client handler is to test the getTag()
button.addClickHandler(clientHandler)
return app
}
function key(e){
var app = UiApp.getActiveApplication();
var panel=app.getElementById('panel');
var txt2=app.getElementById('txt2');
var txt1val = e.parameter.txt1;//gets textBox value
panel.setTag(txt1val);// set tag value
txt2.setStyleAttribute('background','white');// resets to white when entering text
return app
}
I think the problem here is that the button click is using a client handler. The setText() only accepts a static string, which is being set to "start empty". If you want to set it to the current value of the tag then you'll need to use a server handler instead.