Access widgets in a VerticalPanel in Google Apps Scripts? - google-apps-script

Is there a way to access the widgets within a panel in GAS?
Something like:
function clickHandler_(e) {
var app = UiApp.getActiveApplication();
var panel = app.getElementById(e.parameter.whatever); // short-cutting here
for (var i=0; i<panel.widgets.length; i++) {
var widget = panel.widgets[i];
// do something with them
}
return app;
}

There isn't something simple like this.
You have to give all widgets an id when adding them and save them somewhere so you can retrieve it later. For example, as a tag on the panel:
function doGet(e) {
var app = UiApp.createApplication();
...
var panel = app.createXYZPanel().setId('mypanel');
var IDs = ''; //list of child widgets on this panel
panel.add(widget.setId('id1'));
IDs += ',id1';
panel.add(widget2.setId('id2'));
IDs += ',id2';
//and so on...
panel.setTag(IDs); //save the IDs list as a tag on the panel
...
return app;
}
//...later on a handler
function handler(e) {
var app = UiApp.getActiveApplication();
//the panel or a parent must be added as callback of the handler
var IDs = e.parameter.mypanel_tag.split(',');
for( var i = 1; i < IDs.length; ++i ) { //skipping the 1st empty element
var widget = app.getElementById(IDs[i]);
//do something
}
return app;
}
By the way, you probably know that, but your code had a mistake:
It's not UiApp.getElementById, but app.getElementById.

Related

GUI form does not show up

I'm stuck... My code doesn't want to show me my GUI form.
If I'm trying to add this line: sh=SpreadsheetApp.getActive() before sh.show(app) - script works fine in the script editor. But! If I'm trying to deploy as a web app - script doesn't work.
function doGet() {
var app = UiApp.createApplication().setTitle('test online').setHeight(400).setWidth(600);
var dFormat = UiApp.DateTimeFormat.DATE_LONG
var sh = SpreadsheetApp.openById(spreadsheetID);
var settings = sh.getSheetByName('Name');
var lastrowTime = sh.getSheetByName('Name').getLastRow();
var settings = settings.getRange("A2:A" + lastrowTime).getValues();
var main2 = app.createGrid(1, 4);
var status = app.createLabel().setId('status').setWidth('200');
var card = app.createTextBox().setName('card').setId('card').setWidth('50');
var main1 = app.createGrid(6, 3);
var placeA = app.createTextBox().setId('placeA').setName('placeA').setWidth('400');
var placeB = app.createTextBox().setId('placeB').setName('placeB').setWidth('400');
var phone = app.createTextBox().setId(('phone')).setName('phone').setWidth('200');
var timeTo = app.createListBox(false).setWidth(200).setName('timeTo').addItem("...").setVisibleItemCount(1);
for (var i = 0; i < settings.length; i++) {
timeTo.addItem(settings[i]);
}
var main = app.createGrid(4, 5);
var date = app.createDateBox().setName('date').setFormat(dFormat);
var hour = app.createListBox().setName('hour').setWidth('100');
var min = app.createListBox().setName('min').setWidth('100');
for (h=0;h<24;++h){
if(h<10){var hourstr='0'+h}else{var hourstr=h.toString()}
hour.addItem(hourstr)
}
for (m=0;m<60;++m){
if(m<10){var minstr='0'+m}else{var minstr=m.toString()}
min.addItem(minstr)
}
var refresh = app.createButton('Refresh')
var button = app.createButton('Submit')
var main3 = app.createGrid(1,3);
var price = app.createLabel().setId('price').setWidth('400');
var finalStatus = app.createLabel().setId('finalPrice').setWidth('400');
main2.setWidget(0,0, app.createLabel('Client card: ')).setWidget(0,1, card).setWidget(0,3, status);
main1.setWidget(1,0, app.createLabel('From')).setWidget(1,1,placeA);
main1.setWidget(2,0, app.createLabel('To')).setWidget(2,1,placeB);
main1.setWidget(4,0, app.createLabel('Mobile')).setWidget(4,1,phone);
main1.setWidget(5,0, app.createLabel('Make a call?')).setWidget(5,1,timeTo);
main.setWidget(1,0,app.createLabel('Data')).setWidget(1,1,app.createLabel('hour')).setWidget(1,2,app.createLabel('min'))
main.setWidget(2,0,date).setWidget(2,1,hour).setWidget(2,2,min)
main.setWidget(2,3,refresh).setWidget(2,4, button)
main3.setWidget(0,0, price);
main3.setWidget(0,1, finalStatus);
var serverHandler = app.createServerHandler('show').addCallbackElement(main).addCallbackElement(main1).addCallbackElement(main2).addCallbackElement(main3);
button.addClickHandler(serverHandler)
var handler1 = app.createServerHandler('refresh').addCallbackElement(main).addCallbackElement(main1).addCallbackElement(main2).addCallbackElement(main3);
refresh.addClickHandler(handler1)
var handler2 = app.createServerHandler('checkDate').addCallbackElement(main).addCallbackElement(main1).addCallbackElement(main2).addCallbackElement(main3);
date.addValueChangeHandler(handler2)
app.add(main2)
app.add(main1)
app.add(main)
app.add(main3)
sh.show(app)
}
The methods that you are using to show your UI are specifically for Spreadsheet containers. You've probably read this, to get where you are, but re-read Creating User Interface Elements in UI Service, especially the examples of doGet().
function doGet() { // A script with a user interface that is published as a web app
// must contain a doGet(e) function.
...
return myapp;
}
At the end of the function, you simply need to return your UI App instance. No need to call show, or reference the spreadsheet at all.

nested listbox google apps script

Im trying to workaround the impossibility of creating nested listboxes in GAS. I created some arrays to populate the listboxes and used the for looping to connect them to their respective listboxes.
The Arrays
var TicketTypeArray=["TICKETTYPE1","TICKETTYPE2","TICKETTYPE3","TICKETTYPE4","TICKETTYPE5","TICKETTYPE6","TICKETTYPE7","TICKETTYPE8","TICKETTYPE9","TICKETTYPE10","TICKETTYPE11","TICKETTYPE12","TICKETTYPE13","TICKETTYPE14"];
var DemandedByArray=["DEMANDEDBY1","DEMANDEDBY1"];
var AnalystArray=["ANALYST1","ANALYST2","ANALYST3","ANALYST4","ANALYST5","ANALYST6","ANALYST7","ANALYST8","ANALYST9"];
var StatusType1Array=["STATUS1","STATUS2","STATUS3"];
var StatusType2Array=["STATUS1","STATUS2","STATUS3"];
var StatusType3Array=["STATUS1","STATUS2","STATUS3"];
Im trying to use a if else loop to nest the next listbox to another one:
if (TicketTypeListBox="TICKETTYPE1")
{
for(var i=0; i<StatusType1Array.length; i++)
{
StatusListBox.addItem(appRegistro.createLabel(StatusType1Array[i])).setItemText(i, StatusType1Array[i]);
}
}
else if (TicketTypeListBox="TICKETTYPE2")
{
for(var i=0; i<StatusType2Array.length; i++)
{
StatusListBox.addItem(appRegistro.createLabel(StatusType2Array[i])).setItemText(i, StatusType2Array[i]);
}
}
else
{
StatusListBox.addItem("Teste");
}
The TicketTypeListBox is:
var TicketTypeListBox = appRegistro.createListBox().setId('TicketType').setName('TicketType');
for(var i=0; i<TicketTypeArray.length; i++)
{
TicketTypeListBox.addItem(appRegistro.createLabel(TicketTypeArray[i])).setItemText(i, TicketTypeArray[i]);
}
To show the panel, im using the code:
panel.add(DataLabel);
panel.add(DataTextBox);
panel.add(TicketIDLabel);
panel.add(TicketIDTextBox);
panel.add(TicketTypeLabel);
panel.add(TicketTypeListBox);
panel.add(DemandedByLabel);
panel.add(DemandedByListBox);
panel.add(AnalystLabel);
panel.add(AnalystListBox);
panel.add(StatusLabel);
panel.add(StatusListBox);
appRegistro.add(panel);
return appRegistro
Now, when i run the script in a Google Sites, i get the error message "Cannot find method add(string)". When debuging, it locates the error just in the line of the TicketTypeListBox.
panel.add(TicketTypeListBox);
What can i do?
This error is coming because in the if block, instead of equality comparator, you have used assignment operator which assigns a string to the list box.
Istead of
if (TicketTypeListBox = "ACCESS CONTROL")
use
if (TicketTypeListBox == "ACCESS CONTROL")
Similarly modify other places also.
I am pretty much sure that still it will not work because, a listbox can not be compared with a string. To create nested listboxes, you will have to use change handler on the lisboxes. Here is a quick example for the same.
var lbArray1 = ['item1', 'item2', 'item3'];
var item1Array = ['item11', 'item12', 'item13'];
var item2Array = ['item21', 'item22', 'item23'];
var item3Array = ['item31', 'item32', 'item33', 'item34'];
function doGet(e){
var app = UiApp.createApplication();
var vPanel = app.createVerticalPanel();
app.add(vPanel);
var lb1 = app.createListBox().setName('lb1').setId('lb1');
for(var i in lbArray1){lb1.addItem(lbArray1[i])};
var lb2 = app.createListBox().setName('lb2').setId('lb2');
//load the 2nd lb with items dependent on first one
for(var i in item1Array){lb2.addItem(item1Array[i])};
vPanel.add(lb1).add(lb2);
//use change handler to dynamically change the 2nd listbox based on the selection of first listbox
var changeHandler1 = app.createServerHandler('updateListBox_').addCallbackElement(vPanel);
lb1.addChangeHandler(changeHandler1);
return app;
}
function updateListBox_(e){
var app = UiApp.getActiveApplication();
//first listbox item selected
var lb1Item = e.parameter.lb1;
var lb2 = app.getElementById('lb2');
lb2.clear();//remove all items from lb2
if(lb1Item == 'item1'){
for(var i in item1Array){lb2.addItem(item1Array[i])};
}
else if(lb1Item == 'item2'){
for(var i in item2Array){lb2.addItem(item2Array[i])};
}
else if(lb1Item == 'item3'){
for(var i in item3Array){lb2.addItem(item3Array[i])};
}
return app;
}

How to make the last item added to a flextable return directly underneath the previous item?

Could someone please show me how to make the last item added to the flextable go directly underneath the existing item?
function doGet() {
var app = UiApp.createApplication();
var listBox = app.createListBox();
listBox.addItem("item 1").addItem("item 2").addItem("item 3").setName("myListBox");
var handler = app.createServerHandler("buttonHandler");
// pass the listbox into the handler function as a parameter
handler.addCallbackElement(listBox);
var table = app.createFlexTable().setId("myTable");
var button = app.createButton("+", handler);
app.add(listBox);
app.add(button);
app.add(table);
return app;
}
function buttonHandler(e) {
var app = UiApp.getActiveApplication();
app.getElementById("myTable").insertRow(0).insertCell( 0, 0).setText( 0, 0, e.parameter.myListBox);
return app;
}
The idea is to keep in memory the index of the row you write to. One can use many ways to achieve this but the most straightforward is probably to write this row index in the table or listBox tag, something like this (I didn't test the code but hope I made no error):
EDIT : the tag solution didn't seem to work, so I changed to a hidden widget solution.(tested)
function doGet() {
var app = UiApp.createApplication();
var listBox = app.createListBox();
listBox.addItem("item 1").addItem("item 2").addItem("item 3").setName("myListBox");
var hidden = app.createHidden().setName('hidden').setId('hidden')
var handler = app.createServerHandler("buttonHandler");
// pass the listbox into the handler function as a parameter and the hidden widget as well
handler.addCallbackElement(listBox).addCallbackElement(hidden);
var table = app.createFlexTable().setId("myTable");
var button = app.createButton("+", handler);
app.add(listBox).add(button).add(table).add(hidden);// add all widgets to the app
return app;
}
function buttonHandler(e) {
var app = UiApp.getActiveApplication();
var pos = e.parameter.hidden;// get the position (is a string)
if(pos==null){pos='0'};// initial condition, hidden widget is empty
pos=Number(pos);// convert to number
var table = app.getElementById("myTable")
table.insertRow(pos).insertCell( pos, 0).setText(pos, 0, e.parameter.myListBox);// add the new item at the right place
++pos ;// increment position
app.getElementById('hidden').setValue(pos);// save value
return app;// update app
}
The following piece of code is doing the same, but with the usage of a ScriptProperties method. Nothing fancy to it and works like a charm:
function doGet() {
var app = UiApp.createApplication();
// create listBox and add items
var listBox = app.createListBox().setName("myListBox")
.addItem("item 1")
.addItem("item 2")
.addItem("item 3");
// set position
ScriptProperties.setProperty('position', '0');
// create handle for button
var handler = app.createServerHandler("buttonHandler").addCallbackElement(listBox);
// create flexTable and button
var table = app.createFlexTable().setId("myTable");
var button = app.createButton("+", handler);
// add all to application
app.add(listBox)
.add(button)
.add(table);
// return to application
return app;
}
function buttonHandler(e) {
var app = UiApp.getActiveApplication();
// get position
var position = parseInt(ScriptProperties.getProperty('position'),10);
// get myTable and add
app.getElementById("myTable").insertRow(position).insertCell(position, 0).setText(position, 0, e.parameter.myListBox);
// increment position
var newPosition = position++;
// set position
ScriptProperties.setProperty('position', newPosition.toString());
// return to application
return app;
}
Please de-bug the code, so that the authorization process is initiated.
Reference: Script and User Properties

Getting both listbox text and value in server handler

I have the following code where I try to fill a listbox with the addItem(text,value) method to store both a text and an index and I would like to retriebe both when the listbox is clicked.
As a default e.parameter.listObject returns the text. Is there anyway to retriebe also the value?
The code is as follows:
function doGet() {
var app = UiApp.createApplication().setTitle('New app');
var listHandler = app.createServerKeyHandler('listSelect');
var lb = app.createListBox(true).setId('lbColour').setName('lbColour').setVisibleItemCount(10).addClickHandler(listHandler);
lb.addItem("RED","100");
lb.addItem("GREEN","010");
lb.addItem("BLUE","001");
// Create a vertical panel and add the grid to the panel
var panel = app.createVerticalPanel();
panel.setId('mainPanel');
panel.add(lb);
panel.add(app.createLabel('Colour Code:'));
panel.add(app.createTextBox().setName('colourCode').setId('colourCode'));
panel.add(app.createLabel('Colour Name:'));
panel.add(app.createTextBox().setName('colourName').setId('colourName'));
app.add(panel);
return app;
}
function listSelect(e){
var app = UiApp.getActiveApplication();
app.getElementById('colourName').setText(e.parameter.lbColour);
app.getElementById('colourCode').setText(e.parameter['lbColour_Value']);
return(app);
}
Thanks to the below answers this will be the corrected code for the above one.
Note that as stated we can store the key at the text property using a valid separator, I used here "|" which is not as usual to find in an string as ",".
Also note that the main to problems on the original code for this to work was that the lisbox cannot be created as a multiselect, that is:
We shall use "createListBox()" instead of "createListBox(true)"
The server handler shall be created using "createServerHandler" instead of "creteServerKeyHandler".
The final code should look like this:
function doGet() {
var app = UiApp.createApplication().setTitle('New app');
var listHandler = app.createServerHandler('listSelect');
var lb = app.createListBox().setId('lbColour').setName('lbColour').setVisibleItemCount(10).addClickHandler(listHandler);
lb.addItem("RED","RED|100");
lb.addItem("GREEN","GREEN|010");
lb.addItem("BLUE","BLUE|001");
// Create a vertical panel and add the grid to the panel
var panel = app.createVerticalPanel();
panel.setId('mainPanel');
panel.add(lb);
panel.add(app.createLabel('Colour Code:'));
panel.add(app.createTextBox().setName('colourCode').setId('colourCode'));
panel.add(app.createLabel('Colour Name:'));
panel.add(app.createTextBox().setName('colourName').setId('colourName'));
app.add(panel);
return app;
}
function listSelect(e){
var app = UiApp.getActiveApplication();
var outpArr = e.parameter.lbColour.split('|');
app.getElementById('colourName').setText(outpArr[0]);
app.getElementById('colourCode').setText(outpArr[1]);
return(app);
}
You can't retrieve the text of your list box item.
What you can do is this,
Reformat your list items like this:
lb.addItem("BLUE","001,BLUE");
And then
e.parameter.lbColour.split(',')[0]
Will return 001
And
e.parameter.lbColour.split(',')[1]
Will return BLUE
Good luck
function doGet() {
var app = UiApp.createApplication();
var vp = app.createVerticalPanel().setId("vp");
var lb = app.createListBox().setName("lb");
lb.addItem("BLUE", "BLUE,001");
vp.add(lb);
var handler = app.createServerHandler('postFunc').addCallbackElement(vp);
var button = app.createButton("Send").addClickHandler(handler);
vp.add(button);
app.add(vp);
return app;
}
function postFunc(e){
var app = UiApp.getActiveApplication();
var vp = app.getElementById("vp");
var outpArr = e.parameter.lb.split(',');
var color = app.createLabel("color text: " + outpArr[0]);
var number = app.createLabel("color number: " + outpArr[1]);
vp.add(color);
vp.add(number);
return app;
}
EDIT 2 : Thomas's answer wasn't wrong after all... The handler used in the original question was the cause of the issue and the multiple selection enable suffers also of an issue -see last comments (issue 941).
So I removed my first answer (to make it shorter) and suggest here a possible workaround to use multiple selection on a listBox (tested and working)
function doGet() {
var app = UiApp.createApplication().setTitle('New app');
var listHandler = app.createServerHandler('listSelect');
var lb = app.createListBox(true).setWidth('200').setId('lbColour').setName('lbColour').setVisibleItemCount(5).addChangeHandler(listHandler);
var colorIndex = [];// this is the array that will hold item values values with 2 fields
lb.addItem("RED");colorIndex.push("RED-100");// use a '-' separator to be able to split later First field-second field
lb.addItem("GREEN");colorIndex.push("GREEN-010");
lb.addItem("BLUE");colorIndex.push("BLUE-001");
lb.setTag(colorIndex.toString());// store the array in the TAG
var panel = app.createVerticalPanel();
panel.setId('mainPanel');
panel.add(lb);
panel.add(app.createLabel('Colour Code:'));
panel.add(app.createTextBox().setWidth('500').setName('colourCode').setId('colourCode'));
panel.add(app.createLabel('Colour Name:'));
panel.add(app.createTextBox().setWidth('500').setName('colourName').setId('colourName'));
app.add(panel);
return app;
}
function listSelect(e){
var app = UiApp.getActiveApplication();
var codepos = [];//will get the index position of items in colorIndex
var code = [];//array of first items found in colorIndex
var color = [];// index of second items found in colorIndex
var colorIndex = e.parameter.lbColour_tag.split(',');// recover the array
for(n=0;n<colorIndex.length;++n){if(e.parameter.lbColour.match(colorIndex[n].split('-')[0]) == colorIndex[n].split('-')[0]){codepos.push(n)}};// get the position in the array
for(n=0;n<codepos.length;++n){
code[n] = colorIndex[codepos[n]].split('-')[1];// finally get the color code value
color[n] = colorIndex[codepos[n]].split('-')[0];// get the color value
}
app.getElementById('colourName').setText(color.toString());// show the result second field
app.getElementById('colourCode').setText(code.toString()); // show the result first field
return(app);
}

How to use global variables in event handlers

I have a global variable contacts,
var contacts = ContactsApp.getContacts();
I have an KeyUphandler for my textbox called search().
In search(),
contacts[0].getPrimaryEmail() gives out an error, "Cant call method getPrimaryEmail of undefined"
However contacts[0].getPrimaryEmail() works fine in other normal functions.
Cant we use global variables in event handlers?
The code is given below,
//CODE starts
var contacts = ContactsApp.getContacts();
function ShowAuto() {
var app = UiApp.createApplication().setTitle('Simple Autocomplete');
var Panel = app.createVerticalPanel().setId('mainPanel').setWidth('555px');
var textBox = app.createTextBox().setName('textBox').setWidth('330px').setId('textBox');
var tBoxHandler = app.createServerKeyHandler('search');
tBoxHandler.addCallbackElement(textBox);
textBox.addKeyUpHandler(tBoxHandler);
var listBox = app.createListBox().setName('list').setWidth("330px").setId('list').setVisibleItemCount(5)
.setStyleAttribute("border", "1px solid white")
.setStyleAttribute("background", "white").setVisible(false);
Panel.add(app.createLabel().setId('label'));
Panel.add(textBox);
Panel.add(listBox);
app.add(Panel);
var ownerEmail = Session.getActiveUser().getEmail();
// I used this method first.. It din work.
// var contacts = ContactsApp.getContacts();
// for (var i in contacts)
// {
// var emailStr = contacts[i].getPrimaryEmail();
// conts[i]=emailStr;
// Browser.msgBox(emailStr);
// }
//These two calls are just to test, the contacts Array, They work fine :)
getContact(0);
getContact(1);
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.show(app);
}
function getContact(n)
{
// Browser.msgBox(contacts[n].getPrimaryEmail());
Logger.log(contacts[n].getPrimaryEmail()); //works fine
return contacts[n].getPrimaryEmail();
}
function search(e)
{
var app = UiApp.getActiveApplication();
//Logger.log(contacts[0].getPrimaryEmail()); //Not working
app.getElementById('label').setText(e.parameter.textBox.toString());
// app.getElementById('label').setText(conts[0]);
app.getElementById('list').setVisible(true);
var searchKey = new RegExp(e.parameter.textBox.toString(),"gi");
if (searchKey == "")
app.getElementById('textBox').setValue('');
var listBoxCount = 0;
for (i=0;i<5;i++){
//Here is where i get error
if(contacts[i].indexOf(e.parameter.textBox)!=-1 && listBoxCount < 5)
{
app.getElementById('list').addItem(conts[i]);
listBoxCount++;
}
}
}
Sorry if i was unclear
Well, I have used UserProperties.setProperty and getProperty function to avoid the use of global variables.
Whenever a server handler is invoked, the global variables will be recalculated as given in https://developers.google.com/apps-script/class_serverhandler, so mine was not working properly.
Thanks for the support guys :)