I have 3 Radio buttons in my Ui in the same Radio group. They are,
var rbutton1 = app.createRadioButton('dist','5 miles');
var rbutton2 = app.createRadioButton('dist','10 miles');
var rbutton3 = app.createRadioButton('dist','25 miles');
In the event handler function, the variable, e.parameter.dist gives true or false just based on whether rbutton3 (the last radio button) is checked or not. Is there any way to determine what radio button is selected exactly?
The only way the make radio buttons group work like this (as intended by design) is by using them in a FormPanel and looking the name (in your case "dist") on a doPost from a submit action of the form.
There's some workarounds though, using the new client handlers that make it radio buttons usage on any panel roughly the same as on the from. Please take a look at this issue on the tracker. You may want to star this issue as well, to keep track of updates and kind of vote for it.
I use:
eventData.parameter.source
and pick up the change using addClickHandler.
You need to store this somewhere
Are these buttons suppose to be in an exclusive OR mode. If so, they need to have the same name. Look at Serge's answer for a detailed explanation and example code.
in the meantime I came up with a workaround to set the radiobutton as well, in this example I use a listBox but any other data could be used.
Here is the complete code : (to test in a spreadsheet container)
function radiotest() {
var app = UiApp.createApplication();
var panel = app.createVerticalPanel();
var radioValue = app.createTextBox().setId('radioValue');
radioValue.setId("radioValue").setName("radioValue");
var listhandler = app.createServerHandler('listhandler').addCallbackElement(panel);
var list = app.createListBox().addChangeHandler(listhandler).setName('list');
for(var i = 1; i < 10; i++){
var name = 'choice '+i;
list.addItem('Activate '+name,name)
var handler = app.createClientHandler().forTargets(radioValue).setText(name);
panel.add(app.createRadioButton('radioButtonGroup',name).addValueChangeHandler(handler).setId(name));
}
panel.add(radioValue);
var getit=app.createButton("Valide").setId("val");
panel.add(getit).add(list)
var handler = app.createServerHandler("valide")
handler.addCallbackElement(panel)
getit.addClickHandler(handler);
app.add(panel);
SpreadsheetApp.getActiveSpreadsheet().show(app);// show app
}
//
function valide(e){ ;// This function is called when key "validate" is pressed
var sh = SpreadsheetApp.getActiveSheet();
var RadioButton = e.parameter.radioValue;
sh.getRange('A1').setValue(RadioButton);
var app = UiApp.getActiveApplication();
return app;
}
function listhandler(e){ ;// This function is called when listBox is changed
var sh = SpreadsheetApp.getActiveSheet();
var app = UiApp.getActiveApplication();
var listvalue = e.parameter.list
var radioValue = app.getElementById('radioValue').setValue(listvalue)
sh.getRange('A2').setValue(listvalue);
var radiobutton = app.getElementById(listvalue)
radiobutton.setValue(true)
return app;
}
the selected radioButton values comes in the textBox value and the listBox allows to select which radioButton is activated... it shows up like this
There is also another approach, as stated by eddyparkinson that is to use the e.parameter.source but this works only if the handler is assigned directly to the radioButton and not using a 'submit' button. In many case it can be used and makes the code a(little) bit lighter.
Here is a test of this code
function radiotest2() {
var app = UiApp.createApplication();
var panel = app.createVerticalPanel();
var listhandler = app.createServerHandler('listhandler2').addCallbackElement(panel);
var list = app.createListBox().addChangeHandler(listhandler).setName('list');
var handler = app.createServerHandler("valide2")
handler.addCallbackElement(panel)
for(var i = 1; i < 10; i++){
var name = 'choice '+i;
list.addItem('Activate '+name,name)
panel.add(app.createRadioButton('radioButtonGroup',name).setId(name).addClickHandler(handler));
}
panel.add(list)
app.add(panel);
SpreadsheetApp.getActiveSpreadsheet().show(app);// show app
}
function valide2(e){ ;// This function is called when a radioButton is selected
var sh = SpreadsheetApp.getActiveSheet();
var source = e.parameter.source;
var radioValue = '';
if(source.match('choice')=='choice'){radioValue=source}
sh.getRange('A1').setValue(radioValue);
var app = UiApp.getActiveApplication();
return app;
}
function listhandler2(e){ ;// This function is called when listBox is changed
var sh = SpreadsheetApp.getActiveSheet();
var app = UiApp.getActiveApplication();
var listvalue = e.parameter.list
sh.getRange('A2').setValue(listvalue);
var radiobutton = app.getElementById(listvalue)
radiobutton.setValue(true)
return app;
}
Related
I built an app in which I use ScriptProperties to store data from a handler to its but(e) function. This was working nice, until other people started using the same spreadsheet at the same time. So often happens that one person is taking a time thinking about what item choose from a checkbox menu and another person uses the same function, changing the data stored at scriptProperties and affecting the use of the function by the first person.
What is the best way to fix it, using an alternative way to pass information through the handler?
Here one sample of one of theese functions (in which I'm using ScriptProperties to pass the values ofletterSpreadsheetId and recipientArray):
function letter(letterSpreadsheetId){
ScriptProperties.setProperty('letterSpreadsheetId', letterSpreadsheetId); // different people may have different letterSpreadsheetId;
ScriptProperties.setProperty('letter', 1); // to be used in another function
var activeSheet = ss.getActiveSheet();
var app = UiApp.createApplication().setHeight(400).setWidth(600);
var panel = app.createVerticalPanel(); // you can embed that in a form panel
var label = app.createLabel("Choose a receiver").setStyleAttribute("fontSize", 18);
app.add(label);
var sheet = SpreadsheetApp.openById(letterSpreadsheetId).getSheetByName("receivers");
var recipientArray = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
var item3Panel = app.createHorizontalPanel();
item3Panel.add(app.createLabel("receiver"));
var listBox = app.createListBox().setName('item3');
for(var i = 0; i < (recipientArray.length); i++){
listBox.addItem(recipientArray[i][1]);
}
item3Panel.add(listBox);
var recipientArrayStr = JSON.stringify(recipientArray);
ScriptProperties.setProperty('recipientArr', recipientArrayStr);
var handlerBut = app.createServerHandler("butAnswerLetter").addCallbackElement(panel);
var but = app.createButton("submit").setId("submitButton2").addClickHandler(handlerBut);
panel.add(item1Panel)
.add(item2Panel)
.add(item3Panel)
.add(but)
.add(app.createLabel().setId("answer"));
var scroll = app.createScrollPanel().setPixelSize(600, 400).setTitle("My title 1");
scroll.add(panel);
app.add(scroll);
ss.show(app);
}
function butAnswerLetter(e){
var letterSpreadsheetId = ScriptProperties.getProperty('letterSpreadsheetId');
var recipient = e.parameter.item3;
ScriptProperties.setProperty('recipient', recipient);
var recipientArrayRecovery = ScriptProperties.getProperty('recipientArr');
var recipientArray = JSON.parse(recipientArrayRecovery);
for(var i=0;i<recipientArray.length;i++){
if(recipient == recipientArray[i][1]){
var usedRecipientArray = recipientArray[i];
}
}
You have 2 possibilities (that I know), either use userProperties instead of script-Properties as these are associated with the user but it will require the user to login and authorize, or - and this will work in every case even if the app is accessed anonymously, use the tags that you can write on almost any widget.
the syntax is quite simple, here is a small code example :
function doGet(){
var app = UiApp.createApplication().setTitle('test_TAG');
var list = app.createListBox(true).setVisibleItemCount(5).setPixelSize(30,450).setName('list');
var handler = app.createServerHandler('show').addCallbackElement(list);
list.addChangeHandler(handler);
var data = [];
for(var n = 0;n<20;n++){
list.addItem(n+' ');
data.push('available value = '+Number(n+1));
}
list.setTag(data.toString());
app.add(list);
return app
}
function show(e){
var app = UiApp.getActiveApplication();
var data = e.parameter.list_tag.split()
var selected = e.parameter.list;
app.add(app.createTextBox().setText(selected).setPixelSize(200,20));
app.add(app.createTextArea().setText(data.join()).setPixelSize(200,300));
return app;
}
testable here
Edit
following Zig's pertinent comment :
I forgot to mention the hidden widget (or a textBox / area set to invisible, useful for debugging when you want to check what it contains ! ) that is also useable of course...
The comment about a user having multiple windows showing the same app is also worth mentioning !
All in all you have 3 possibilities after all !
(thanks to Zig Mandel)
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.
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.
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);
}
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).