Listbox not opening handler - google-apps-script

I am trying to create a menu in a listbox format where user chooses an option and then another uiapp is shown with the info they selected. I am having an issue here that when I opened google gives me an error that says Error encountered. An expected error occurred. I think it has to do with the setId part, if I remove one of the setId's the error doesnt happen. is this even possible?
function doGet(e) {
var app = UiApp.createApplication().setTitle("Services");
var dropDownList = app.createListBox().setName('list').setId('list');
var infoLabel = app.createLabel('Scroll around to select the service desired').setId('infoLabel');
var panel = app.createVerticalPanel();
//addItem fills the list
dropDownList.addItem("Option 1").setId("add");
dropDownList.addItem("Option 2");
panel.add(dropDownList);
panel.add(infoLabel);
app.add(panel);
var info = app.getElementById("add");
var handler2 = app.createServerHandler('display2');
info.addClickHandler(handler2);
app.add(dropDownList);
app.add(infoLabel);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function display2(e) {
var app = UiApp.createApplication();
var html = app.add(app.createHTML("<p><p><b>You have selected this option</b> </p>")).setHeight(220).setWidth(220);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
return app;
}

Are you deploying this as a web app or a script within a Spreadsheet?
If you are deploying this as a web app, then SpreadsheetApp.getActiveSpreadsheet() will not work - replace it with SpreadsheetApp.openById(id) where id is your spreadsheet ID which you will find in the URL when you open the file in the browser.
If you want to deploy this w/in a spreadsheet through a menu item or a simple button, then that works as is.
I was able to just copy paste your code and get the listbox part working fine -
https://docs.google.com/spreadsheet/ccc?key=0AkJNj_IM2wiPdHRYQThlaGVVSk04R052ZGNqclhEZWc#gid=0
Update -
I now understand what you are trying to do. Couple of things - you want to make sure you are adding a callback element via handler.addCallbackElement(myWidget) otherwise, you will not be able to read the value of the element. Second thing is that you don't need a server handler on each option in a dropdown list. Just having one handler will fire it for every change and you'll be able to get the option you selected.
I've cleaned up the code here below and also updated the spreadsheet to use this code.
function showUI() {
var app = UiApp.createApplication().setTitle("GeekSquad Services");
var infoLabel = app.createLabel('Scroll around to select the service desired');
var dropDownList = app.createListBox().setName('list').setId('list');
dropDownList.addItem("Option 1");
dropDownList.addItem("Option 2");
//you can add as many options here manually or dynamically
var handler = app.createServerHandler('dropDownCallback')
handler.addCallbackElement(dropDownList);
dropDownList.addClickHandler(handler);
var panel = app.createVerticalPanel();
panel.add(dropDownList);
panel.add(infoLabel);
app.add(panel);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function dropDownCallback(e) {
var app = UiApp.createApplication();
var html = app.add(app.createHTML("<b>You have selected this option</b> " + e.parameter.list));
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
Update #2 -
If you want to fork off and create different app instances thats easy (though its unclear why wouldn't just change panels).
function dropDownCallback(e) {
if(e.parameter.list === 'Option 1'){
var app = UiApp.createApplication();
var html = app.add(app.createHTML("Here for option!"));
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
else if (e.parameter.list ==== 'Option 2'){
//create and show other App here or whatever else
}
//refactor this better to not repeat code.
}

Related

Changing the content of a UI element with a button press

I have a Google UI App that is designed to assist entering data into a google spreadsheet where most of the rows are identical. To do so an UI panel is created and text fields are used for each column of the spreadsheet. With the push of a button these values are appended to the spreadsheet.
I would like the button to then clear the contents of a single text box and maintain the values of the others to allow for entry of the next nearly identical item. My attempts to do so have resulted in "Error encountered: Object does not allow properties to be added or changed."
I have included a simplified version below, the actual script has many text boxes but this simplified example also shares the problem.
function showNewEntryDialog() {
var app = UiApp.createApplication();
app.setTitle("Add Multiple Items");
var panel = app.createVerticalPanel();
var grid = app.createGrid(9,3);
var nameTextBox = app.createTextBox();
var serialTextBox = app.createTextBox();
nameTextBox.setName('nameTextBox').setId('nameTextBox');
serialTextBox.setName('serialTextBox').setId('serialTextBox');
var snButton = app.createButton('Add and New SN')
grid.setWidget(0,0,app.createLabel('Name'));
grid.setWidget(0,1,nameTextBox);
grid.setWidget(4,0,app.createLabel('Serial Number'));
grid.setWidget(4,1,serialTextBox);
grid.setWidget(4,2,snButton);
panel.add(grid);
var snClickHandler = app.createServerClickHandler("respondToButtonPress");
snButton.addClickHandler(snClickHandler);
snClickHandler.addCallbackElement(panel);
app.add(panel);
var doc = SpreadsheetApp.getActive();
doc.show(app);
}
function respondToButtonPress(e) {
var app = UiApp.getActiveApplication();
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow([e.parameter.nameTextBox,
e.parameter.serialTextBox]);
app.getElementById('serialTextBox').setText('');
// Does nothing?
app.getElementById('serialTextBox').setValue='';
// Error:Object does not allow changes
}
How can a button change the contents of a text box in the UI panel? If it cannot, in what other way can I achieve the behavior I desire?
Bryan's answer is correct but you could do it more efficiently using a clientHandler in your main function like this :
function showNewEntryDialog() {
var app = UiApp.createApplication();
app.setTitle("Add Multiple Items");
var panel = app.createVerticalPanel();
var grid = app.createGrid(9,3);
var nameTextBox = app.createTextBox();
var serialTextBox = app.createTextBox();
nameTextBox.setName('nameTextBox').setId('nameTextBox');
serialTextBox.setName('serialTextBox').setId('serialTextBox');
var snButton = app.createButton('Add and New SN')
grid.setWidget(0,0,app.createLabel('Name'));
grid.setWidget(0,1,nameTextBox);
grid.setWidget(4,0,app.createLabel('Serial Number'));
grid.setWidget(4,1,serialTextBox);
grid.setWidget(4,2,snButton);
panel.add(grid);
// a clientHandler : ----------------------------------------------------------
var cHandler = app.createClientHandler().forTargets(serialTextBox).setText('');
//-----------------------------------------------------------------------------
var snClickHandler = app.createServerClickHandler("respondToButtonPress");
snButton.addClickHandler(snClickHandler).addClickHandler(cHandler);// you can have multiple handlers on the same widget
snClickHandler.addCallbackElement(panel);
app.add(panel);
var doc = SpreadsheetApp.getActive();
doc.show(app);
}
function respondToButtonPress(e) {
var app = UiApp.getActiveApplication();
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow([e.parameter.nameTextBox,
e.parameter.serialTextBox]);
}
Think .setText() and .setValue() essentially do the same thing so really only need one or the other. Your .setValue='' is invalid. Should be .setValue(''). Need a return app; at the end too.

Google Apps Script Persistence

Background: I have a google site and I have been pulling information from a google spreadsheet containing the marks of my students, however I'd like to make it more dynamic so that they can request a report of all of their current marks whenever they'd like. In the script that I've written, students will enter a password, click a button and then their marks will be generated.
Issue: From what I've read, when they click the button, the handler for the button causes the script to be re-run. The current spreadsheet cannot be stored and when I try to access the spreadsheet, it tells me that it is null. How can I get access to the spreadsheet again? I've tried using ScriptProperties, but I got the same result. By the way, it works if I do not try to run it as a webapp.
Here's the doGet() function and part of the getPassword() function that is called once the button on the UI is pressed.
function doGet() {
var app = UiApp.createApplication();
app.add(app.loadComponent("MyGui"));
var panel = app.getElementById("VerticalPanel1");
var text = app.createPasswordTextBox().setName("text");
var handler = app.createServerHandler("getResults").addCallbackElement(text);
panel.add(text);
panel.add(app.createButton("Get Record", handler));
SpreadsheetApp.getActiveSpreadsheet().show(app);
}
function getResults(eventInfo) {
var app = UiApp.createApplication();
var password = eventInfo.parameter.text;
var panel = app.getElementById("VerticalPanel1");
var textArea = app.createRichTextArea();
panel.add(textArea);
var pointsSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var passwordCheckRange = pointsSheet.getRange("B70:C94").getValues();
...
The problem probably is that when the script is run as a webapp theres no "activeSpreadSheet"
so
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
fails. An alternate aproach will be to pass the SpreadSheet id in hidden field to the call back.
In your doGet function:
var hidden = app.createHidden('ssId', 'YOUR_SS_ID_HERE');
panel.add(hidden);
var handler = app.createServerHandler("getResults").addCallbackElement(text);
handler.addCallbackElement(hidden)`
In your callback function
var ssID = eventInfo.parameter.ssId;
var pointsSheet = SpreadsheetApp.openById(ssID).getSheetByName('SHEET_NAME');

Dropdown list with error

I have this UiApp that presents the user with two different options in a dropdown list. User has two options when they select one of them another uiapp comes and displays some information, and it has a button to go back to original Menu. The problem I am having is that when I make the choices I get an error that says "Error Encountered: An unexpected error occurred." and the same happens when I click the go back button on the option 1 and 2. Is this a bug of GAS, or is it code that shouldnt be there?
Thank you!
function Menu(e) {
var app = UiApp.createApplication().setTitle(" Title");
var dropDownList = app.createListBox().setName('list').setId('list');
var infoLabel = app.createLabel('Scroll around to select the service desired').setId('infoLabel');
//addItems
dropDownList.addItem("Options");
dropDownList.addItem("Option1");
dropDownList.addItem("Option2");
var handler = app.createServerClickHandler('changeMe');
handler.addCallbackElement(dropDownList);
dropDownList.addChangeHandler(handler);
app.add(dropDownList);
app.add(infoLabel);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
return app;
}
function changeMe(e) {
if(e.parameter.list === 'Option1'){
var app1 = UiApp.createApplication();
var html1 = app1.add(app1.createHTML("<p><i>Hello you have selected Option 1</i> </p>")).setHeight(800).setWidth(600);
var button1 = app1.createButton('Go back').setId("button1");
app1.add(button1);
var handler2 = app1.createServerHandler('Menu');
button1.addClickHandler(handler2);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app1);
return app1;
}
else if (e.parameter.list === 'Option2'){;
var app2 = UiApp.createApplication();
var html2 = app2.add(app2.createHTML("Hello You have selected Option2"));
var button2 = app2.createButton('Go back').setId("button");
app2.add(button2);
var handler2 = app2.createServerHandler('Menu');
button2.addClickHandler(handler2);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app2);
return app2;
}
}
Try removing the 3 "return app" lines. This seems to work for me. The show method will automatically put it on the spreadsheet.

How To Allow Users to Review Answers before Submiting Form?

I've built a form with UiApp to collect information from the user. It's rather complex with multiple panels and file uploads, so I would like to give the user the opportunity review their inputs before submitting. I was hoping to display their inputs on one final review panel that would then allow them to decide to edit the info and move back to a earlier panel to edit.
Following is the test script. The farthest I've gotten is getting it to return 'textBox' and not the value of the textBox. Is it possible to get the values while staying in the doGet portion of my script, or must I move to doPost to access the values?
What would be the work around you would suggest?
Thanks for any and all help!
function doGet(e){
var app = UiApp.createApplication();
var appPanel = app.createVerticalPanel();
var form = app.createFormPanel();
var panel1 = app.createHorizontalPanel();
var emailLabel = app.createLabel('Your Email');
var email = app.createTextBox().setName('email').setId('email');
app.add(form);
var button1 = app.createButton('Go to Review');
panel1.add(emailLabel);
panel1.add(email);
panel1.add(button1);
appPanel.add(panel1);
form.add(appPanel);
var panel2 = app.createHorizontalPanel().setVisible(false);
var reviewLabel = app.createLabel('Your Email:');
var reviewEmail = app.createLabel(email);
panel2.add(reviewLabel);
panel2.add(reviewEmail);
appPanel.add(panel2);
//
var reviewPageTwo = app.createClientHandler()
.forTargets(panel1).setVisible(false)
.forTargets(panel2).setVisible(true);
button1.addClickHandler(reviewPageTwo);
return app;
}
UPDATE 8.24.12
I'm including the resulting script. It includes the review function, the button to lead the user back to edit, and the submitButton to post it. (You will need to replace the spreadsheet ID for the post to work.)
Thank for the help all!
Martin
function doGet(e){
var app = UiApp.createApplication();
var appPanel = app.createVerticalPanel();
var form = app.createFormPanel();
var panel1 = app.createHorizontalPanel().setId('panel1');
var emailLabel = app.createLabel('Your Email');
var email = app.createTextBox().setName('email').setId('email');
var syncChangeHandler = app.createServerHandler('syncText').addCallbackElement(form);
app.add(form);
var button1 = app.createButton('Go to Review');
panel1.add(emailLabel);
panel1.add(email);
panel1.add(button1);
appPanel.add(panel1);
form.add(appPanel);
var panel2 = app.createHorizontalPanel().setId('panel2').setVisible(false);
var reviewGrid = app.createGrid(3,3).setId('reviewGrid');
var reviewEmail = app.createLabel().setId('reviewEmail');
var reviewLabel = app.createLabel('Your Email:');
var submitButton = app.createSubmitButton('Submit');
var button2 = app.createButton('Edit Response');
panel2.add(reviewLabel);
panel2.add(reviewEmail);
panel2.add(button2);
panel2.add(submitButton);
appPanel.add(panel2);
//
var editResponse = app.createClientHandler()
.forTargets(panel1).setVisible(true)
.forTargets(panel2).setVisible(false);
button1.addClickHandler(syncChangeHandler);
button2.addClickHandler(editResponse);
return app;
}
function syncChangeHandler(e){
var app = UiApp.getActiveApplication();
app.getElementById('reviewEmail').setText(e.parameter.email);
app.getElementById('panel1').setVisible(false);
app.getElementById('panel2').setVisible(true);
return app;
}
function doPost(e){
var ss = SpreadsheetApp.openById('*your spreadsheet id here*').getSheets()[0];
var range = ss.getRange(ss.getLastRow()+1, 1, 1,2);
var values = [[new Date(),e.parameter.email]];
range.setValues(values);
var app = UiApp.getActiveApplication();
var label = app.createLabel('Thank You!');
app.add(label);
return app;
}
You have your entire function inside doGet(). The doGet() function is executed when your UI is first loaded.
So,
var email = app.createTextBox().setName('email').setId('email');
actually resolves to a text box. When you do
var reviewEmail = app.createLabel(email);
you are trying to pass a text box as an argument to createLabel, which is not allowed. Therefore this won't work. You must handle the changes to the text box in a handler.
function doGet(){
var syncChangeHandler = app.createServerHandler('syncText').addCallbackElement(form);
var email = app.createTextBox().setName('email').setId('email');
...
var reviewEmail = app.createLabel().setId('reviewEmail');
...
}
function syncText(e){
var app = UiApp.getActiveAplication();
app.getElementById('reviewEmail').setText(e.parameter.email);
return app;
}
What Srik said is true (of course ;-)), you can't indeed assign a label this type of value... What I would do (since you work in a doGet/doPost structure) is to create a second button just aside of the submit button that triggers a handler to a 'review' function that populates all the corresponding textBoxes , listBoxes or whatever you have with the values coming from your main form (a sort of copy of it) in the review panel that you already have. To achieve this you will need to add the form as a callBackElement to the handler (which was not necessary with the doPost scheme).
Another option could be to add this handler to all the widgets separately with 'key up' triggers or 'Value change triggers' so that the review panel is always up to date in real time, in this case the 'review before submit panel' could be visible at any time without further action from the user other than make it eventually visible (although it could also be always visible). In this option the handler function would be more like a 'synchroniser'. I'm afraid you'll have some difficulties with file upload though (since this can only work in a doGet/doPost structure).

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.