I want to use a web page to gather data in stages. When the user clicks next I want to have the current panel removed from the app and a new panel added and I am having trouble making it work.
Here is my current code:
function doGet() {
//Create Application
var app = UiApp.createApplication();
//Set Application Title
app.setTitle("New Lead Form");
/////////////////////////////////Step 1///////////////////////////////////
// Create a Panel for Step 1 Data
var nextbuttonstep1 = app.createButton("Next");
// Create the entry form, a 6 x 2 grid with text boxes for First Name, Last Name, Phone Number, Email, & Property State that is then added to a vertical panel
var grid = app.createGrid(5, 2);
var propertystate = app.createListBox().setName('propertystate').setId('propertystate')
propertystate.addItem('AL');
propertystate.addItem('FL');
propertystate.addItem('IN');
propertystate.addItem('KY');
grid.setWidget(0, 0, app.createLabel('First Name:'));
grid.setWidget(0, 1, app.createTextBox().setName('firstname').setId('firstname'));
grid.setWidget(1, 0, app.createLabel('Last Name:'));
grid.setWidget(1, 1, app.createTextBox().setName('lastname').setId('lastname'));
grid.setWidget(2, 0, app.createLabel('Phone Number:'));
grid.setWidget(2, 1, app.createTextBox().setName('phonenumber').setId('phonenumber'));
grid.setWidget(3, 0, app.createLabel('Email Address:'));
grid.setWidget(3, 1, app.createTextBox().setName('emailaddress').setId('emailaddress'));
grid.setWidget(4, 0, app.createLabel('Property State:'));
grid.setWidget(4, 1, propertystate);
// Create a vertical panel and add the grid to the panel
var step1panel = app.createFlowPanel();
step1panel.add(grid);
step1panel.add(nextbuttonstep1);
app.add(step1panel)
var handler = app.createServerHandler('proceedtostep2');
handler.addCallbackElement(step1panel)
nextbuttonstep1.addClickHandler(handler);
return app;
}
function proceedtostep2(eventInfo) {
var parameter = eventInfo.parameter;
var panel =parameter.step1panel;
var app = UiApp.getActiveApplication();
app.remove(panel);
return app;
}
Panels don't show up as parameters, only input fields do. You will have to retrieve the panel you want to remove using it's ID:
...
// Create a vertical panel and add the grid to the panel
var step1panel = app.createFlowPanel().setId('someUniqueIdString');
...
and
...
function proceedtostep2(eventInfo) {
var app = UiApp.getActiveApplication();
app.remove(app.getElementById('someUniqueIdString'));
return app;
}
But removing all elements from a application window does not close the window. The Window only remains in a ugly state.
Adding other widgets after the remove is OK but you should use some fresh ID's. If you use old ID strings with new widgets bad things happen.
Related
I'm teaching a class and for my class I keep all of my student's marks on a google spreadsheet. On my website I would like to present information to students on an individual basis. I've created an app where it presents them with a password text box. They type in their password and then it retrieves information from the spreadsheet that is unique to them and presents it to them in a label. I've been trying to hack this all together, but it's just not working properly and I'm getting an error that I cannot diagnose. If I print out the information using Browser.msgBox() it outputs the info, but otherwise it generates an error. Why is this happening and what is the fix? Here's the code:
var pointsSheet = SpreadsheetApp.openById('1o8_f063j1jYZjFEnI_P7uAztpnEAvQ6mc3Z1_Owa69Y');
//creates and shows an app with a label and password text box
function doGet() {
var app = UiApp.createApplication().setTitle('Incomplete Challenges');
var mygrid = app.createGrid(1, 2);
mygrid.setWidget(0, 0, app.createLabel('Password:'));
mygrid.setWidget(0, 1, app.createPasswordTextBox().setName("text"));
var mybutton = app.createButton('Submit');
var submitHandler = app.createServerClickHandler('getResults');
submitHandler.addCallbackElement(mygrid);
mybutton.addClickHandler(submitHandler);
var mypanel = app.createVerticalPanel();
mypanel.add(mygrid);
mypanel.add(mybutton);
app.add(mypanel);
SpreadsheetApp.getActiveSpreadsheet().show(app);
//return app; //UNCOMMENT WHEN DEPLOYING APP
}
//obtains data based on password entered by user and outputs their info
function getResults(eventInfo) {
var app = UiApp.createApplication().setTitle('Incomplete Challenges');
var password = eventInfo.parameter.text;
var passwordCheckRange = pointsSheet.getRange("B34:C34").getValues();
if (passwordCheckRange == null) {
Browser.msgBox("Error: Range is null");
return app;
}
var name;
for(var i = 0; i < passwordCheckRange.length; i++) {
if(passwordCheckRange[i][1] == password) {
name = passwordCheckRange[i][0];
break;
}
}
var studentRecordRange = pointsSheet.getRange("B3:AY29").getValues();
var headingRange = pointsSheet.getRange("B1:AY2").getValues();
if (studentRecordRange == null) {
Browser.msgBox("Error: Range is null");
return app;
}
var requestedRecord;
for(var i = 0; i < studentRecordRange.length; i++) {
if(studentRecordRange[i][0] == name)
requestedRecord = studentRecordRange[i];
}
var stringRecord = "";
for(var i = headingRange[1].length-1; i >= 7; i--) {
if (requestedRecord[i] == "")
stringRecord += headingRange[1][i] + ": " + headingRange[0][i] + "XP" + "\\n";
}
var mygrid = app.createGrid(2, 1);
mygrid.setWidget(0, 0, app.createLabel('INCOMPLETE CHALLENGES'));
mygrid.setWidget(1, 0, app.createLabel(stringRecord));
var mypanel = app.createVerticalPanel();
mypanel.add(mygrid);
app.add(mypanel);
//Browser.msgBox(stringRecord);
return app;
}
The error that I experience is: Error encountered: An unexpected error occurred.
As you can see it's very helpful.
Line 28 it should be getActiveApplication() and not createApplication().
You cant create an application on another application. :)
Also I think line 63 it should be "<br>"; instead "\n"; along with line 68 it should be createHTML instead of createLabel
I also think that you have apply few styling css so that your app looks good. check on .setStyleAttributes in UiApp.
There are a few errors in this code, the first one -that generates the error you get - is (as mentioned in the other answer) the UiApp.createApplication() in the handler function.
You can't create an UiApp instance in a handler function, you should instead get the active instance and eventually add elements to it (using UiApp.getActiveApplication()).
You can't neither change the title of this instance. Btw, it doesn't make sense since this title will not appear as a "title" when you will be deploying this app as a webapp. It will simply show up at the top of your browser window (as a page title) as your app will occupy the whole screen and not a modal popup anymore. So if you want a title to appear in your Ui, simply add it as an HTML widget where you can choose the font size and weight (and any other CSS styles).
The other error is in the password check, you are using Browser.msgBox("Error: Range is null"); but Browser class won't work in UiApp. You should only use UiApp elements, not spreadSheetApp elements.
And, as a more general comment, I suggest you test your app directly using the .dev url (last saved version) of the app (after saving a beta version and having deployed it) so that you are in the "real" use condition and have a pertinent pov on the result.
I have a stackpanel containing 3 panels.
After the user clicks a button on page-1, I want page-2 to become visible.
How can I achieve this?
Edit-1
As I thought the question I asked really is a general one, I did not provide code.
But Serge insas and Zig Mandell asked for code, so here it is.
function doGet()
{
var app = UiApp.createApplication();
var stackPanel = app.createStackPanel().setSize('100%', '100%'); //Create stack panel
var onClick = app.createServerHandler('onClick');
var button = app.createButton('Button on second panel...').setId('btnPageTwo').addClickHandler(onClick);
//add widgets to each stack panel, and name the stacked panels
stackPanel.add(app.createLabel('Text on first panel...'), 'One');
stackPanel.add(button, 'Two');
stackPanel.add(app.createLabel('Text on third Panel...'), 'Three');
app.add(stackPanel); //Add the panel to the application
return app;
}
function onClick(e)
{
Logger.log('In onClick --> show stackPanel "Three" now');
}
In this example I would like to show panel Two at startup and after clicking the button I would like to show panel 3.
I tried using focusPanels, but that didn't help
function doGet()
{
var app = UiApp.createApplication();
var stackPanel = app.createStackPanel().setSize('100%', '100%'); //Create stack panel
// FocusPanels are limited to contain ONE widget
var focusOne = app.createFocusPanel().setId('focusOne');
var focusTwo = app.createFocusPanel().setId('focusTwo');
var focusThree = app.createFocusPanel().setId('focusThree');
// Create panels to overcome the one-widget-limitation of focuspanels
var ver = app.createVerticalPanel().setId('ver');
var hor = app.createHorizontalPanel().setId('hor');
var tab = app.createTabPanel().setId('tab');
var tabOne = app.createVerticalPanel().setId('tabOne');
var tabTwo = app.createHorizontalPanel().setId('tabTwo');
tab.add(tabOne, 'One').add(tabTwo, 'Two');
focusOne.add(ver);
focusTwo.add(hor);
focusThree.add(tab);
var labOne = app.createLabel('Text on first panel...');
var labThree = app.createLabel('Text on second tab of third panel...');
var onClick = app.createServerHandler('onClick');
var button = app.createButton('Button on second panel...').setId('btnPageTwo').addClickHandler(onClick);
ver.add(labOne);
hor.add(button);
tabTwo.add(labThree);
tab.selectTab(1); // Select second tab
//add widgets to each stack panel, and name the stack panel
stackPanel.add(focusOne, 'stackOne').add(focusTwo, 'stackTwo').add(focusThree, 'stackThree');
app.add(stackPanel); //Add the panel to the application
return app;
}
function onClick(e)
{
Logger.log('In onClick --> show focusPanel "Three" now');
var app = UiApp.getActiveApplication();
var focusThree = app.getElementById('focusThree');
focusThree.setFocus(true);
return app;
}
Unfortunately this has been the subject of an enhancement request for quite a while (dec 2012) but I'm afraid Google won't do anything about it since they stopped UiApp development (they recommend switching to HTMLService).
You could eventually use tabPanel instead, this one has all the necessary features.
Test here
code below :
function doGet() {
var app = UiApp.createApplication();
var tabPanel = app.createTabPanel().setSize('100%', '100%').setId('tabP'); //Create tab panel
var onClick = app.createServerHandler('onClick');
var button = app.createButton('Button on second panel...').setId('btnPageTwo').addClickHandler(onClick);
//add widgets to each tab panel, and name the tabed panels
tabPanel.add(app.createLabel('Text on first panel...'), 'One');
tabPanel.add(button, 'Two');
tabPanel.add(app.createLabel('Text on third Panel...'), 'Three');
app.add(tabPanel); //Add the panel to the application
tabPanel.selectTab(1);
return app;
}
function onClick(e){
var app = UiApp.getActiveApplication();
var tabP = app.getElementById('tabP').selectTab(2);
return app;
}
I have the following code which creates a simple app to allow the user to enter two values and click a button:
function start() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var app = UiApp.createApplication();
app.setTitle("Appraisals Analysis");
app.setHeight(100);
app.setWidth(500);
var grid = app.createGrid(3, 2);
grid.setId("grid");
grid.setCellSpacing(2);
grid.setCellPadding(2);
var uafLabel = app.createLabel("Unprocessed apparaisals folder name: ");
uafLabel.setStyleAttributes({"font-weight": "bold"});
var uafTextBox = app.createTextBox();
uafTextBox.setName('uafTextBox').setId('uafTextBox');
uafTextBox.setText('Unprocessed Appraisals');
grid.setWidget(0, 0, uafLabel);
grid.setWidget(0, 1, uafTextBox);
var pafLabel = app.createLabel("Processed apparaisals folder name: ");
pafLabel.setStyleAttributes({"font-weight": "bold"});
var pafTextBox = app.createTextBox();
pafTextBox.setName('pafTextBox').setId('pafTextBox');
pafTextBox.setText('Processed Appraisals');
grid.setWidget(1, 0, pafLabel);
grid.setWidget(1, 1, pafTextBox);
var button = app.createButton('Submit').setId("submitButton");
grid.setWidget(2, 0, button);
var mypanel = app.createVerticalPanel();
mypanel.add(grid);
app.add(mypanel);
var clickHandler = app.createServerClickHandler("parseFiles");
button.addClickHandler(clickHandler);
clickHandler.addCallbackElement(grid);
ss.show(app);
}
I then have the parseFiles function which can take up to 2 minutes to do its job as follow
function parseFiles(e) {
var app = UiApp.getActiveApplication();
var processedFolder = DocsList.getFolder(e.parameter.pafTextBox);
var workingFolder = DocsList.getFolder(e.parameter.uafTextBox);
var appraisals = workingFolder.find('Performance Appraisal');
app.getElementById("submitButton").setText("Parsing Files...");
for (var i in appraisals) {
var doc = DocumentApp.openById(appraisals[i].getId());
parseDocument(doc, getEmpName(doc.getName()));
}
return app;
}
My problem is that when I click the button, the work gets done, but the button text stays as "Submit" instead of changing to "Parsing Files...". Once the work is over, then the button changes.
Any idea what I may be doing wrong?
Regards
Crouz
You got nothing wrong, just the concept. Think like this: all Apps Script code you write runs on a google server (server-side), but the interface is (obviously) shown on your computer (client-side). The Apps Script "environment" has a client-side script (that we do not have access or control of) that receives the information on how to build the interface you defined in your code (server-side).
So, everything you do in your code gets updated at once, in a bundle, after your function finishes. And that's why we need to return app, so that our UiApp definition gets sent/returned to the client-side that have triggered the script.
For very simple situations, like disabling or setting the text on a button or label, there's a clientHandler that can perform basic operations on directly the client-side without requiring a network trip to the server-side to run your custom code. Since these operations are done on the client-side they're done "instantly". Note that this is not for generic code, but only predefined operations. clientHandlers are really meant just for simple stuff. It's difficult (if not impossible) to do complex operations.
Here's my suggestion using a clientHandler:
function start() {
//your current code...
clickHandler.addCallbackElement(grid);
var clientHandler = app.createClientHandler().forEventSource().setText('Parsing Files...').setEnabled(false);
button.addClickHandler(clientHandler);
ss.show(app);
}
function parseFiles(e) {
//...
app.getElementById("submitButton").setText("Submit again").setEnabled(true);
//...
return app;
}
Note that you can add multiple handlers, client or server, to a button (or any other widget that accept handlers) and all of them will run concurrently.
Also, it's very important to notice that we're talking about UiApp here, when using HtmlService the approach is significantly different.
EDIT (last ! :-) : A workaround to this is putting the whole UI in an absolutePanel (thx megabyte1024).
It's not perfect since I cannot have a background color in the whole display area but it's at least much more confortable. (link to online test app is updated with this new version) and screen capture of the final version... much better :-)
I have a standalone webapp written in GAS that has a scrollPanel, the whole UI is rather small and occupies only a part of a (even small) display area in the browser window.
What bothers me is that I always have both an horizontal and a vertical scrollbar in the browser window and it interferes with the UI content when I use a mouse or a trackpad to scroll my scrollpanel in the UI window...
So, my question is : is there a way to avoid this, to tell the browser that there is no need to add scrollbars or to define a smaller "webapp area" ?
note that the size of these scroll bars are fully independent from the UI panel size as long as this last one is smaller than the browser window.
Here is a screen capture to illustrate what I'm saying (I do understand that it's a detail but it makes the use of this app sometimes just uncomfortable ;-) Here is also a link to a public version of the app.
Another detail I'd like to solve is the font color in the upper part of this UI : these are textBoxes that I set to 'read only' because I don't want them to be editable (it would give the illusion that the user could modify data which is not the case) and a side effect of this read only status is that fonts are "greyed" ... is there some way to avoid that while keeping the same aspect (except color) on this 'false table' ?
EDIT : found the second question about text color : .setStyleAttribute('color','#000000') as simple as that... too stupid from me not to have found it earlier ;-)
NOTE 2 : interestingly, UI designed with the GUI builder do not suffer the same problem ...
EDIT2 :here is the code of the doGet part (modified to run without functionality but to showup):
var key='0AnqSFd3iikE3dFV3ZlF5enZIV0JQQ0c1a3dWX1dQbGc'
var ss = SpreadsheetApp.openById(key)
var sh = ss.getSheetByName('Sheet1')
var idx = 0
var data = ss.getDataRange().getValues();
var len = data.length;
var scrit = ['All fields','Name','Lastname','Postal Adress','ZIP code','City','Country','email','phone#']
//public version
function doGet() {
var app = UiApp.createApplication().setTitle("BrowseList Test")
.setHeight(420).setWidth(800).setStyleAttribute("background-color","beige").setStyleAttribute('padding','20');
var title = app.createHTML('<B>DATABASE User Interface</B>').setStyleAttribute('color','#888888');
app.add(title);
var scroll = app.createScrollPanel().setPixelSize(750,150)
var vpanel = app.createVerticalPanel();
var cell = new Array();
var cellWidth = [45,135,150,250,50,100]
var row = new Array();
var cHandler = app.createServerHandler('showpicked').addCallbackElement(scroll);
for(vv=0;vv<15;++vv){
row[vv]=app.createHorizontalPanel();
vpanel.add(row[vv]);
for(hh=0;hh<cellWidth.length;++hh){
cell[hh+(vv)*cellWidth.length]=app.createTextBox().setWidth(cellWidth[hh]+"").setTitle('Click to show below')
.setReadOnly(true).setId((hh+vv*cellWidth.length)+'').addClickHandler(cHandler).setStyleAttribute('background','#eeeeff').setStyleAttribute('color','#000000');
row[vv].add(cell[hh+(vv)*cellWidth.length])
}
}
app.add(scroll.add(vpanel))
// Initial populate
var resindex = new Array()
for(vv=0;vv<15;++vv){
resindex.push(vv+1)
for(hh=0;hh<cellWidth.length;++hh){
var rowpos=vv+1+idx
var cellpos = hh+vv*cellWidth.length
cell[cellpos].setValue(data[rowpos][hh]);
}
}
var rHandler = app.createServerHandler('refresh');
//
var slist = app.createListBox().setName('critere').setId('slist').addClickHandler(rHandler);
for(nn=0;nn<scrit.length;++nn){
slist.addItem(scrit[nn]);
}
var search = app.createTextBox().setName('search').setId('search').setTitle('becomes yellow if no match is found');
var modeS = app.createRadioButton('chkmode','strict').setId('chkmodes').addClickHandler(rHandler);
var modeG = app.createRadioButton('chkmode','global').setValue(true).setId('chkmodeg').addClickHandler(rHandler);
var letter = app.createRadioButton('show','letter').setValue(true).setId('letter').addClickHandler(rHandler);
var raw = app.createRadioButton('show','raw data').setId('raw').addClickHandler(rHandler);
var index = app.createHTML('found/'+len).setId('index').setStyleAttribute('color','#aaaaaa');
var grid = app.createGrid(2,10).setWidth('750');
grid.setWidget(1, 0, app.createLabel('search'));
grid.setWidget(1, 1, search);
grid.setWidget(1, 2, modeS);
grid.setWidget(1, 3, modeG);
grid.setWidget(1, 5, slist);
grid.setWidget(1, 6, app.createLabel('show mode'));
grid.setWidget(1, 7, letter);
grid.setWidget(1, 8, raw);
grid.setWidget(1, 9, index);
app.add(grid);
var hidden = app.createHidden('hidden').setId('hidden').setValue(resindex.toString());
cHandler.addCallbackElement(grid).addCallbackElement(scroll).addCallbackElement(hidden);
var result = app.createRichTextArea().setPixelSize(745,160).setId('result')
.setStyleAttribute('background','white').setStyleAttribute('font-family',"Arial, sans-serif")
.setStyleAttribute('font-size','small');
result.setHTML('test ui');
app.add(result).add(hidden);
var sHandler = app.createServerHandler('searchH').addCallbackElement(grid).addCallbackElement(scroll);
search.addKeyUpHandler(sHandler);
rHandler.addCallbackElement(grid).addCallbackElement(scroll);
slist.addChangeHandler(rHandler);
return app
}
A possible solution to get rid of the scroll bar is to use an intermediate Absolute panel. The following code has the scroll bars.
function doGet() {
var app = UiApp.createApplication();
var panel = app.createScrollPanel().setSize('100%', '100%');
var content = app.createButton('Scroll Bars').setSize('100%', '100%');
panel.setWidget(content);
app.add(panel);
return app;
}
And the code bellow has no the scroll bars
function doGet() {
var app = UiApp.createApplication();
var panel = app.createScrollPanel().setSize('100%', '100%');
var subPanel = app.createAbsolutePanel().setSize('100%', '100%');
var content = app.createButton('No Scroll Bars').setSize('100%', '100%');
subPanel.add(content);
panel.setWidget(subPanel);
app.add(panel);
return app;
}
when embedding a HTMLService web app inside a Google Site, it looks like the iFrame gadget has to be about 50 pixels larger than the app height for the vertical scroll bar to disappear. Assuming same thing applies to UiApp.
I have created two user interfaces. How can I close the first one and activate the next? Is it possible to have two UI under Google apps script?
I have try something like:
var app = UiApp.getActiveApplication();
app.add(app.loadComponent("APPGui"));
var panel1 = app.getElementById("LoginPanel1");
panel1.setVisible(false);
return app;
The easiest way is probably to design both panels in the same GUI builder, one over each other in 2 separate panels, the 'login panel' being above the other it will mask the other one when active. As you set it 'invisible', you'll see the one underneath.
Depending on your use case the login panel might hide all or only a part of your main panel.
The GUI builder has all the necessary tools to decide which is in front or backwards.
Here's and example of three dialogs shown one after the other, maintaining state/data between them via the CacheService object.
(You could use UserProperties, ScriptProperties or even a Hidden Field as an alternative, each has their own scope though...)
Hopefully this makes sense without explaining what each dialog in the UI Builder contains.
function showDialog1(){
var app = UiApp.createApplication();
app.add( app.loadComponent("Dialog1") );
SpreadsheetApp.getActiveSpreadsheet().show(app);
}
function onDialog1OKButton(e){
CacheService.getPrivateCache().put("n1", e.parameter.n1);
var app = UiApp.getActiveApplication();
var d2 = app.loadComponent("Dialog2");
app.add(d2);
SpreadsheetApp.getActiveSpreadsheet().show(app);
}
function onDialog2OKButton(e){
var c = CacheService.getPrivateCache();
c.put("n2", e.parameter.n2);
var app = UiApp.getActiveApplication();
app.add(app.loadComponent("DialogResult"));
var n1 = c.get("n1");
var n2 = c.get("n2");
var l = app.getElementById("Label2");
l.setText( "" + n1 + " + " + n2 + " = " + (parseInt(n1) + parseInt(n2)) );
SpreadsheetApp.getActiveSpreadsheet().show(app);
}
I prefer to build multiple GUI. With this code you can jump between them.
function doGet() {
var app = UiApp.createApplication();
var base0 =app.createAbsolutePanel().setId('GUI_base0').setHeight('630px').setWidth('1125px');
app.createAbsolutePanel().setId('GUI_base1'); // create all abs_panells but not use
// you need to create all abspanels if you want to jump between them
app.createAbsolutePanel().setId('GUI_base2'); // create here all the absolute panels (1 for every GUI)
// app.createAbsolutePanel() ... GUI3, GUI4 ...
var component0 = app.loadComponent("GUI_password"); // load first GUI (his name is "password"
/// this is an example of code for the 1st GUI ////////////////////
/// I can check if the user can see the second GUI
var label_ID = app.getElementById('LB_ID');
var user = Session.getActiveUser().getEmail();
if ( user == 'XXX#yyyy.com' ) {
label_ID.setText(user).setTag(user); // only show if ....
}
////////////////////////////////////////////////////////////////////
base0.add(component0); // GUI_password over absolute panel
app.add(base0);
// handler Button1 // we can show a button only if the password is correct or is a valid user or ...
app.getElementById('BT_jump').addClickHandler(app.createServerHandler('NOW_gui1'));
return app;
};
function NOW_gui1(e) {
var app = UiApp.getActiveApplication();
var base0 = app.getElementById("GUI_base0").setVisible(false); // hide 1st abs_panel created with code
var base2 = app.getElementById("GUI_base2").setVisible(false); // hide 3rd abs_panel created with code
/// hide all others abs_panel
var base1 = app.createAbsolutePanel().setId('GUI_base1').setHeight('630px').setWidth('1125px'); // maybe get by ID ??, but this work
var component1 = app.loadComponent("GUI_1"); // load the second GUI
base1.add(component1); // load GUI_1 over 2n absolute panel
app.add(base1);
// HERE THE CODE OF THE GUI_1
// handler Button2
app.getElementById('BT_jump_1_to_2').addClickHandler(app.createServerHandler('NOW_gui2'));
return app;
};