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
Related
I need global variable in google script, to hold page ID, like a string. Here, they suggested to use object Hidden for this purpose. I can create this object and set its value.
Code to achieve the same :
function doGet(e) {
var app = UiApp.createApplication();
//Get current indentificator
var mid = 'page-id';
app.add( app.createHidden('mid').setValue(mid).setId('mid'));
return app;
}
But how can I get this value from another function?
For example :
function maketbl(){
var app = UiApp.getActiveApplication();
app.(?!)
}
Thanks!
I see that your requirement is to have functionality similar to that of global variables. I suggest that you use script properties, user properties or the cache service to accomplish this feat. An example is below
ScriptProperties.setProperty('special', 'sauce'); // Use this to set the property
var specialValue = ScriptProperties.getProperty('special'); // use this to access the property
Try the bellow code:
function doGet(e) {
var app = UiApp.createApplication();
var mid = 'page-id';
var hidden = app.createHidden('mid').setValue(mid).setId('mid');
app.add(hidden);
//Create your handler
var handler = app.createServerHandler('maketbl');
handler.addCallbackElement(hidden)
//Create a button to trigger your function
app.add(app.createButton().setText("go forest").addClickHandler(handler));
return app;
}
function maketbl(e){
var app = UiApp.getActiveApplication();
//Retrieve the hidden field
var hidden = app.getElementById("mid");
//Show the value stored at e.parameter.mid where mid is the name of the field
var dialogBox = app.createDialogBox().setText(e.parameter.mid);
dialogBox.setPopupPosition(100, 100);
dialogBox.show();
return app;
}
Live version here.
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;
}
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).
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.