How to use global variables in event handlers - google-apps-script

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 :)

Related

Message Box in popup form

I have a doubt,
I make a Button for Submit form and when I press the first button appear to me a message of confirmation to continue or not, but when I press "no" and the message disappear and return to form,but the first button doesn't work more.
You known that I make wrong.
thanks for all.
function doGet(){
var app = UiApp.createApplication().setTitle('Request Form');
var flow = app.createFlowPanel().setStyleAttribute("textAlign", "center").setWidth("900px");
var buttons = app.createButton('Print and Save').setId('buttons');
var handlers = app.createServerClickHandler('question').addCallbackElement(flow);
buttons.addClickHandler(handlers);
flow.add(buttons);
app.add(flow);
return app;
}
function question(e){
var app = UiApp.getActiveApplication();
var dialog = app.createPopupPanel().setModal(true).setSize(700, 100).setPopupPosition(10, 400);
var closeHandler = app.createClientHandler().forTargets(dialog).setVisible(false);
var handlersend = app.createServerClickHandler('Send');
var handleract =app.createServerClickHandler('activateAgain');
var labelp = app.createLabel('Are you sure that this information is correct?')
var buttonp1= app.createButton('yes, continue').addClickHandler(closeHandler).addClickHandler(handlersend);
var buttonp2= app.createButton('No, I want correct').addClickHandler(closeHandler).addClickHandler(handleract);
app.getElementById('buttons').setEnabled(false);
var gridp = app.createGrid(1,5);
gridp.setWidget(0, 0, labelp)
.setWidget(0,1,buttonp1)
.setWidget(0,2,buttonp2);
dialog.add(gridp)
dialog.show();
return app;
}
function activateAgain(e){
var app = UiApp.getActiveApplication();
app.getElementById('buttons').setEnabled(true);
return app;
}
function Send(e){
}
You are trying to make it happen in clientHandler which is basically a good idea but the problem is that some methods are not available in clientHandlers, for example hide() is not !
So you used setVisible(false) to hide the dialog but then you can't get it visible again easily...
All in all, I found it was easier with serverHandler and a few modifications.
Since you seem to be quite comfortable with UiApp I don't need to explain further, the code is self explanatory enough (I hope :-)
Code below : (and test HERE)
function doGet(){
var app = UiApp.createApplication().setTitle('Request Form');
var flow = app.createFlowPanel().setStyleAttribute("textAlign", "center").setWidth("900px");
var buttons = app.createButton('Print and Save').setId('buttons');
var handlers = app.createServerClickHandler('question').addCallbackElement(flow);
buttons.addClickHandler(handlers);
flow.add(buttons);
app.add(flow);
return app;
}
function question(e){
var app = UiApp.getActiveApplication();
var dialog = app.createPopupPanel().setModal(true).setSize(700, 100).setPopupPosition(10, 400).setId('dialog');
var button = app.getElementById('buttons').setEnabled(true);
var closeHandler = app.createClientHandler().forTargets(button).setEnabled(true);
var correctHandler = app.createServerHandler('hideDialog');
var handlersend = app.createServerClickHandler('Send');
var labelp = app.createLabel('Are you sure that this information is correct?')
var buttonp1= app.createButton('yes, continue').addClickHandler(handlersend);
var buttonp2= app.createButton('No, I want correct').addClickHandler(closeHandler).addClickHandler(correctHandler);
app.getElementById('buttons').setEnabled(false);
var gridp = app.createGrid(1,5);
gridp.setWidget(0, 0, labelp)
.setWidget(0,1,buttonp1)
.setWidget(0,2,buttonp2);
dialog.add(gridp)
dialog.show();
return app;
}
function hideDialog(){
var app = UiApp.getActiveApplication();
var dialog = app.getElementById('dialog');
dialog.hide();
return app;
}
function Send(e){
var app = UiApp.getActiveApplication();
var dialog = app.getElementById('dialog');
dialog.hide();
app.getElementById('buttons').setEnabled(false);
app.add(app.createLabel('send'));
return app;
}

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.

How to access source widget's value in GAS?

I'm trying to write a generic server handler for textboxes which highlights the text when the textbox gains focus:
function onFocusHighlight(e) {
var app = UiApp.getActiveApplication();
var widget = app.getElementById(e.parameter.source);
var widgetValue = e.parameter.widgetName; // how can I get widgetName from source???
widget.setSelectionRange(0, widgetValue.length);
return app;
}
Can I determine widgetValue from e.parameter.source?
var widget = app.getElementById(e.parameter.source).setName("widgetName")
Then get widgetValue:
var widgetValue = e.parameter.widgetName
Check https://developers.google.com/apps-script/uiapp for usage of setName.
I've found that as long as widgetName and widgetId are the same I can determine widgetValue as follows:
function onFocusHighlight(e) {
var app = UiApp.getActiveApplication();
var widgetId = e.parameter.source;
var widgetName = widgetId; // name MUST match id to retrieve widget value
var widgetValue = e.parameter[widgetName]; // not sure why this syntax works???
var widget = app.getElementById(widgetId);
widget.setSelectionRange(0,widgetValue.length);
return app;
}
My only issue now is understanding how/why the e.parameter[widgetName] syntax actually works. It would also be great to have a solution which doesn't rely on widgetName and widgetId being the same value.
Just a suggestion following your own answer :
You could probably make it work by having a conversion table somewhere that gets the widgets name from their ID.
For example you could define :
ScriptProperties.setProperties({'widgetID1':'Name1','widgetID2':'name2'},true)
and then
widgetvalue = e.parameter[ScriptProperties.getProperty(e.parameter.source)]
I didn't test this code but it seems logical ;-), tell me if it doesn't (I don't have time to test it now)
EDIT : works as expected, here is a test sheet to show the result, test code below.
function Test(){
var app = UiApp.createApplication().setTitle('test');
app.add(app.createLabel('Type anything in upper textBox and then move your mouse over it...'))
var p = app.createVerticalPanel()
var txt1 = app.createTextBox().setId('txt1').setName('Name1').setValue('value in TextBox1')
var txt2 = app.createTextBox().setId('txt2').setName('Name2').setValue('waiting to mouseOver textBox1')
p.add(txt1).add(txt2)
app.add(p)
var handler = app.createServerHandler('mouseOver').addCallbackElement(p);
txt1.addMouseOverHandler(handler);
ScriptProperties.setProperties({'txt1':'Name1','txt2':'Name2'},true);//save the name, only txt1 will be used here
var ss=SpreadsheetApp.getActiveSpreadsheet()
ss.show(app)
}
function mouseOver(e) {
var app = UiApp.getActiveApplication();
var widget = app.getElementById(e.parameter.source);
var widgetValue = e.parameter[ScriptProperties.getProperty(e.parameter.source)]; // ScriptProperties knows the Widget's name
app.getElementById('txt2').setValue(widgetValue) ;// Use the other widget to show result
return app;
}

google apps script TextBox value not passed to e.parameter.TextBoxName

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

Instant search using TextBox and onKeyUp and onKeyDown

I would like to make Google Apps Script webapp that search spreadsheet instantly as you type in the TextBox.
The implementation I"m using now is following. For each key up event new search is lauched with the current search string. I'm using delay on the function that onKeyUp is calling and if the onKeyDown is called I try to kill the previous function call and just go with the new. This works sometimes but the search takes different time to complete so the last one to finish is not allways the right one.
I don't know how to solve this task in Google Apps script. This can be implemented using something like this but these functions are not awailable:
var timer;
function up(){
setTimeout(mySpreadsheetSearchFunction, 500);
}
function down(){
clearTimeout (timer);
}
This is the code of the current implementation that is not working correctly:
function up(){
var cache = CacheService.getPrivateCache();
var now = parseInt(cache.get('iterate'));
Utilities.sleep(500);
if(parseInt(cache.get('iterate')) !== parseInt(now)){
return;
}else{
search();
}
showInGui();
}
function down(){
var cache = CacheService.getPrivateCache();
cache.put('iterate', 1+parseInt(cache.get('iterate')));
}
The CacheService is probably wrong tool for this job, what could be beter? Is this the right way to implement something like this?
Not sure you have to use the cache feature to get a fast result... I made such a script that works pretty well, I use variants of it all the time ;-)
you can test it on this test sheet
here is how it works, the script is maybe quite long but look only at the handler part (click)
On other version I read the spreadsheet outside of the function so the array of data becomes a global variable and mostly to limit the number of spreadsheet calls (it can hit the quota limit in this version sometimes)
// G. Variables
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var lastrow = ss.getLastRow();
// ...
function onOpen() {
var menuEntries = [ {name: "Search GUI", functionName: "searchUI"},
];
ss.addMenu("Search Utilities",menuEntries);// custom menu
}
// Build a simple UI to enter search item and show results + activate result's row
function searchUI() {
var app = UiApp.createApplication().setHeight(130).setWidth(400);
app.setTitle("Search by name / lastname / adress");
var panel = app.createVerticalPanel();
var txtBox = app.createTextBox().setFocus(true);
var label=app.createLabel(" Item to search for :")
panel.add(label);
txtBox.setId("item").setName("item");
var label0=app.createLabel("Row").setWidth("40");
var label1=app.createLabel("Name").setWidth("120");
var label2=app.createLabel("Lastname").setWidth("120");
var label3=app.createLabel("Street").setWidth("120");
var hpanel = app.createHorizontalPanel();
hpanel.add(label0).add(label1).add(label2).add(label3)
//
var txt0=app.createTextBox().setId("lab0").setName("0").setWidth("40");
var txt1=app.createTextBox().setId("lab1").setName("txt1").setWidth("120");
var txt2=app.createTextBox().setId("lab2").setName("txt2").setWidth("120");
var txt3=app.createTextBox().setId("lab3").setName("txt3").setWidth("120");
var hpanel2 = app.createHorizontalPanel();
hpanel2.add(txt0).add(txt1).add(txt2).add(txt3)
var hidden = app.createHidden().setName("hidden").setId("hidden");
var subbtn = app.createButton("next ?").setId("next").setWidth("250");
panel.add(txtBox);
panel.add(subbtn);
panel.add(hidden);
panel.add(hpanel);
panel.add(hpanel2);
var keyHandler = app.createServerHandler("click");
txtBox.addKeyUpHandler(keyHandler)
keyHandler.addCallbackElement(panel);
//
var submitHandler = app.createServerHandler("next");
subbtn.addClickHandler(submitHandler);
submitHandler.addCallbackElement(panel);
//
app.add(panel);
ss.show(app);
}
//
function click(e){
var row=ss.getActiveRange().getRowIndex();
var app = UiApp.getActiveApplication();
var txtBox = app.getElementById("item");
var subbtn = app.getElementById("next").setText("next ?")
var txt0=app.getElementById("lab0").setText('--');
var txt1=app.getElementById("lab1").setText('no match').setStyleAttribute("background", "white");// default value to start with
var txt2=app.getElementById("lab2").setText('');
var txt3=app.getElementById("lab3").setText('');
var item=e.parameter.item.toLowerCase(); // item to search for
var hidden=app.getElementById("hidden")
var data = sh.getRange(2,2,lastrow,3).getValues();// get the 3 columns of data
for(nn=0;nn<data.length;++nn){ ;// iterate trough
Logger.log(data[nn])
if(data[nn].toString().toLowerCase().match(item.toString())==item.toString()&&item!=''){;// if a match is found in one of the 3 fields, break the loop and show results
txt0.setText(nn+2);
txt1.setText(data[nn][0]).setStyleAttribute("background", "cyan");
txt2.setText(data[nn][1]);
txt3.setText(data[nn][2]);
sh.getRange(nn+2,2).activate();
subbtn.setText("found '"+item+"' in row "+Number(nn+2)+", next ?");
hidden.setValue(nn.toString())
break
}
}
return app ;// update UI
}
function next(e){
var row=ss.getActiveRange().getRowIndex();
var app = UiApp.getActiveApplication();
var txtBox = app.getElementById("item");
var subbtn = app.getElementById("next").setText("no other match")
var hidden=app.getElementById("hidden");
var start=Number(e.parameter.hidden)+1;//returns the last search index stored in the UI
var item=e.parameter.item.toLowerCase(); // item to search for
var txt0=app.getElementById("lab0");
var txt1=app.getElementById("lab1").setStyleAttribute("background", "yellow");
var txt2=app.getElementById("lab2");
var txt3=app.getElementById("lab3");
var data = sh.getRange(2,2,lastrow,3).getValues();// get the 3 columns of data
for(nn=start;nn<data.length;++nn){ ;// iterate trough
if(data[nn].toString().toLowerCase().match(item.toString())==item.toString()&&item!=''){;// if a match is found in one of the 3 fields, break the loop and show results
txt0.setText(nn+2);
txt1.setText(data[nn][0]).setStyleAttribute("background", "cyan");
txt2.setText(data[nn][1]);
txt3.setText(data[nn][2]);
sh.getRange(nn+2,2).activate();
subbtn.setText("found '"+item+"' in row "+Number(nn+2)+", next ?");
hidden.setValue(nn.toString())
break
}
}
return app ;// update UI
}
Using the HtmlService you can instead implement this in pure HTML and JavaScript. This gives you the ability to load the spreadsheet data once, and then do the search client-side, where the performance should be better.