Client validation of multiple widgets - google-apps-script

I have a submit button whose enablement state depends on several other widgets' state; and I can't come up with a client side solution in Google Apps Script to do the validation.
For example, take three checkboxes. The submit button should be enabled iff (if-and-only-if) at least one checkbox is enabled.
I know I could do this with server side validation but there shouldn't be any need for something this simple. Any suggestions? Thanks.

It perfectly possible to write client side handlers that depend on multiple widgets' states, as you can just chain many validateX calls on a single handler. The problem here is just that clientHandlers cannot validate checkboxes state.
I have opened an issue regarding this problem, you may want to star it to keep track of updates and kind of vote for it:
Issue 2220: UiApp handler validateValue of checkbox
Anyway, it is possible to workaround this, and I'll just to show you that it is possible to have handlers depending on multiple widgets value, but this code will be much simpler when issue 2220 is solved:
function doGet(e) {
var app = UiApp.createApplication().setTitle('Checkbox Test');
var panel = app.createVerticalPanel(),
noChecked = app.createClientHandler(),
button = app.createButton('Test').setEnabled(false);
for( var i = 0; i < 3; ++i ) {
var cb1 = app.createCheckBox('cb'+i),
cb2 = app.createCheckBox('cb'+i).setVisible(false),
tb = app.createTextBox().setValue('false').setVisible(false);
cb1.addClickHandler(app.createClientHandler().forTargets(cb2).setValue(true).setVisible(true).forEventSource().setVisible(false).forTargets(tb).setText('true'));
cb2.addClickHandler(app.createClientHandler().forTargets(cb1).setValue(false).setVisible(true).forEventSource().setVisible(false).forTargets(tb).setText('false'));
cb1.addClickHandler(app.createClientHandler().forTargets(button).setEnabled(true));
cb2.addClickHandler(noChecked.validateMatches(tb,'false'));
panel.add(cb1).add(cb2).add(tb);
}
noChecked.forTargets(button).setEnabled(false);
return app.add(panel.add(button));
}

ClientHandlers are intentionally simple. You can do arbitrary code with ServerHandlers and the speed difference should be relatively small. Otherwise, yes, this is by design and if you need more complicated client logic you need to use HtmlService.
The tradeoff between UiApp and HtmlService related to how we guarantee that you can't serve malicious code from a script. UiApp code uses simple builder patterns that are limiting but definitely safe, while HtmlService uses complex sandboxing to achieve that goal, with tradeoffs of not working on older browsers and some other limitations.
This specific use case sounds workable in UiApp, if I understand you correctly. If you want an example of a show/hide button that flips, here is one:
function doGet() {
var app = UiApp.createApplication();
var label = app.createLabel("I am a toggleable widget").setVisible(false);
var show = app.createButton("show");
var hide = app.createButton("hide").setVisible(false);
show.addClickHandler(app.createClientHandler()
.forTargets(label, hide).setVisible(true).forTargets(show).setVisible(false));
hide.addClickHandler(app.createClientHandler()
.forTargets(label, hide).setVisible(false).forTargets(show).setVisible(true));
return app.add(show).add(hide).add(label);
}
Basically, use 2 buttons and flip the visibility of the button too.
Checkboxes are in fact validated - but it is their text that is validated, not their value:
var app = UiApp.createApplication();
var check = app.createCheckBox();
check.setText("foo").addClickHandler(
app.createServerHandler("clicked").validateMatches(check, "foo"));
return app.add(check);
The issue tracker request is reasonable though.

Related

Multiple mxGraph on the same page

I have a tabbed web page and I would like to place two different instances of mxGraph, one on the 1st tab and the other one on the 2nd tab:
var editor1 = new mxEditor();
var editor2 = new mxEditor();
I would like to configure each instance in its own way, so the two editors should have different behaviours and properties. Unfortunately, mxGraph is based on lots of static constructs:
mxConstants.EDGE_SELECTION_COLOR = '#a8d6e1';
mxGraphView.prototype.updateFloatingTerminalPoint = function (...) { ... };
mxConnectionHandler.prototype.movePreviewAway = false;
...
It seems that placing two graphs on the same page isn't the right thing to do, since the latest configurations would override others and, maybe, one event can conflict with other ones.
What are you suggesting me?
I'm thinking at completely redrawing graph every time a tab gets focused, but: 1. performances? 2. does mxGraph have a global destroy or reset function? 3. any side effect?...

Mixing Panels in UI Service

I ave been trying to start a new web app project of mine. I wanted to start making my main user interface first using GAS, UI Service.
But after mixing to types of panels, it doesn't seem to work.
What on earth am I doing wrong.
function doGet() {
var app = UiApp.createApplication();
var westPanel = app.createVerticalPanel()
.add(app.createButton("Button 1"))
.add(app.createButton("Button 2"))
.add(app.createButton("Button 3"));
var centerPanel = app.createVerticalPanel()
.add(app.createHTML('Some Text.....'));
var splitPanel = app.createSplitLayoutPanel()
.addWest(westPanel, 150)
.add(centerPanel)
.setHeight('100%').setWidth('100%');
var tabPanel = app.createDecoratedTabPanel()
.add(splitPanel, 'Finances')
.setHeight('100%').setWidth('100%');
app.add(tabPanel);
return app;
}
Kind Regards
Apps Script UiApp uses GWT behind the scenes. And the issue here is that the SplitLayoutPanel (a GWT layout panel) does not work with TabPanel (a non-layout panel). My understanding is that the first asks for the height of the parent which in turn asks for the height of the child. Nobody gets happy and the panels collapse.
If we had in Apps Script the GWT TabLayoutPanel it would be the solution for you. But we don't, and don't even bother requesting any enhancement for UiApp on the issue tracker as they (the Google Apps Script team) have stated multiple times that they're not going to put any effort into it, because the way to go is HtmlService. If you find an unworkable situation, do it in HtmlServices or don't use Apps Script.
Here's a good explanation on why this does not work: gwt ScrollPanel in TabPanel: no vertical scrollbar

google apps script DateItem get dropdown box

So using Google Apps Scripts with a Form, I'm able to get all the items and iterate through them using the following:
var form = FormApp.getActiveForm();
var items = form.getItems();
var item;
for(var i = 0; i < items.length; i++)
{
item = items[i];
if(item.getType() == FormApp.ItemType.DATE)
{
item = item.asDateItem();
item.dropdown.month; // I need a method like this
}
Logger.log("ItemTitle: %s ItemType: %s",items[i].getTitle(), items[i].getType()) ;
}
I can even get the DateItem that I want.
My issue is that I cannot get the dropdown boxes from the DateItem. Does anyone know how to get the dropdown boxes from the DateItem? (Like: item.dropdown.month or item.dropdown.day, etc).
FormApp describes the questions you're asking, not the answers received from people who filled the form out. The type of question is "when were you born?" - nobody answered it yet - there's no date available.
When the form submits, its results go into a spreadsheet. Write a script that parses the spreadsheet's content.
For this kind of dynamic form behavior (a form that responds live as users fill it out), your best bet is to build and HTML user interface using the HTMLService: https://developers.google.com/apps-script/guides/html/
This is significantly more complicated than working with the FormApp Class, but is much more powerful, and eventually allows you to deploy your code as a web app, or publish as an add-on for Sheets, Docs, or Forms.

apps-script: how do I read back the contents of a text widget?

I'm sure this is a really dumb question, but I've spent an hour googling, with no luck.
I'm storing spreadsheet data (some text, some dates) in various widgets. At some point, a click handler has to read back the text or the date from the widget, and write it out to another spreadsheet. Currently, I've got the data in a FlexTable (or in a Label widget in the FlexTable). I've now found out (I think) that there's no way to read back this data.
Any ideas how I'm meant to handle this? I just need a widget that will let me store data, display it, and later read it back.
Thanks.
EDIT
I don't think the answers have actually got me any further forward. I appreciate that I can read the value of some widgets if they're passed to the handler as a callback. However, this appears to be restricted to ones with a setName method - is this just TextBox and ListBox? If so, that's no use, because TextBox is for user entry.
So, is there a widget that will let me (the script, not the user) store data, and later read it back? Or am I just being thick?
You haven't search very well, there are plenty of examples around here... just one posted today : Date AND Time picker Google App Script
and the documentation, look at the (near) end of the page...
You can actually read the text from a Label widget if you set the text in it's tag as well...
// Get Label text and set it to another label
function doGet() {
var app = UiApp.createApplication();
var panel = app.createVerticalPanel();
var label1 = app.createLabel('hello').setTag('hello').setId('label1');
Logger.log(label1.getTag()); // works
var label2 = app.createLabel('').setId('label2');
var handler = app.createServerHandler('myFunction').addCallbackElement(panel);
var btn = app.createButton('Get tag', handler);
panel.add(label1).add(label2).add(btn);
app.add(panel);
return app;
}
function myFunction(e) {
var app = UiApp.getActiveApplication();
Logger.log(app.getElementById('label1').getTag()) // doesn't work
var tag = e.parameter.label1_tag; // works
Logger.log(tag);
app.getElementById('label2').setText(tag); // Sets label2 text as label1 tag
return app;
}
There's no .setName() that you specify with a Label, but it looks like one gets automatically created.
In your case, storing text inside of invisible Text Boxes that coincide with the Labels would be another option.
Have a look at https://developers.google.com/apps-script/uiapp#ServerHandlers
Values of widgets are passed to server handler in e.parameter. Don't forget to give widget Name and Id. If you want to get values of widgets that are not the one triggering the event handled then you need to pass the data using the widget tag value

Global Variables in Chrome Extensions

Is there a simple way where I can access a global javascript variable through content-scripts in chrome extensions?
Accessing global object from content script in chrome extension
I followed the steps mentioned in the above link, but it did not work out for me. Any help would be much appreciated.
Thanks,
Shankar
I managed to complete it. Thanks for the help. I used simple message passing to retrieve the value from the extension script to the content script. The place where I had missed was, the listener at the extension script needs to be at the background page (I think so). Once I changed that, it worked.
For those from the future looking for an answer to this question, here's how I do it:
function getVariable(v) {
var c = document.createElement("div");
c.id = 'var-data';
c.style.display = 'none';
document.body.appendChild(c);
var s = document.createElement('script');
s.innerHTML = 'document.getElementById("var-data").innerText=JSON.stringify('+v+');';
document.head.appendChild(s);
var data = JSON.parse(c.innerText);
c.remove();
s.remove();
return data;
}
And basic usage:
getVariable('globalVarIWantToAccess');
All this script goes in the content-script, not the code for the main webpage, which means that no co-operation is needed from the webpage itself. Basically, the getVariable function creates a script element which is injected into the main page. This script tag retrieves the requested global variable and puts the data into a new div. The function then gets this data from the new div, deletes the new div, deletes the new script element and returns the data.