How can I unShow() after Spreadsheet.show(HtmlOutput)? - google-apps-script

I have this working very nicely :
var theForm = HtmlService.createTemplateFromFile('aForm').evaluate();
var theSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
theSpreadsheet.show(theForm);
When the user has submitted data I want to close "theForm". Is there something like an unShow() or hide() method on a spreadsheet? a harikiri() method on an HtmlOutput?
Ideally, I'd like to have a reShow() command that saves the submitted data, refreshes the spreadsheet and calls show() again.
I have found no way to do these things, so I just disable the submit button. Ugh!
Any suggestions greatly appreciated.
UPDATE (2012/08/28) : I should have mentioned that I am calling back into the same code as originally opened "theForm", from the HTML form, using this call ...
google.script.run.recordTheForm(jsonTheForm);
Do I assume correctly that google.script.run.*() calls have no knowledge of any variable values set earlier?

HtmlService doesn't have an equivalent of app.close()... this is an oversight, not something by design, and we will fix it.
Edit: From within client-side JavaScript code (not from the server like app.close(), but within the HtmlService code itself) you can now call google.script.host.closeDialog()

Call this:
theForm.close();
SpreadsheetApp.flush();
theSpreadsheet.show(theForm);
Good luck,
Thomas van Latum

Related

Google apps script editor's content assist is great, but buggy. Tips n' Tricks? Is this right forum for this?

Is this right place for google script editor questions? Hope so.
Content assist is great, but stops working at times for me. Sometimes fix is to cut/paste all my code in/out of a desktop text editor, and back into google script editor. Perhaps this cleans out hidden chars, tags, etc., or perhaps it resets content assist. Dunno. But, works somewhat. Any thoughts? Tips? Trick?
Too, here's great crash course on google script editor from the developers. Well worth a the watch: Crash Course Apps Scrip Editor
If not appropriate place for editor questions, please point me to it. Thanks.
Also, does GAS stand for google apps script? google apps services? A library? Wha? Yes, I'm kinda newbie. Is there an apps script related wiki?
One trick to reset the content assist is to go back up to the class and retype a period right after it. So, if you're working with a line of code that involves Sheets and it has lost auto-complete for what ever reason, typing the period right after the firstSpreadsheetApp in the function has worked for me.
SO is the wiki.
Please don't use a "GAS" for abbreviation.
I tried re-typing the period after SpreadsheetApp and it didn't seem work for me. I was chaining variables together like this:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(),
open_log = spreadsheet.getSheetByName('OPEN_LOG'),
closed_log = spreadsheet.getSheetByName('CLOSED_LOG'),
invoiced_log = spreadsheet.getSheetByName('INVOICED_LOG');
Once I changed the code to:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var open_log = spreadsheet.getSheetByName('OPEN_LOG');
var closed_log = spreadsheet.getSheetByName('CLOSED_LOG');
var invoiced_log = spreadsheet.getSheetByName('INVOICED_LOG');
the content assist started working again. I'm not 100% positive this is the fix, just seemed to work for me this time. Give it a try...beats pulling your hair out.
This seems to work for me when I lose CONTENT ASSIST [object method prompting] ... for the moment ... the getfid function may somehow be used by the editor when following the actual 2 steps in the function beneath it.
function getfid(fname) {
files = DriveApp.getFilesByName(fname);
file = files.next();
fid = file.getId();
return fid;
}
// the following 2 steps seem to turn CONTENT ASSIST back on ???
// 1. type the period after the SpreadsheetApp [ss must exist?]
// 2. then test to see if TA is back on by typing the period after ss_ad
function resetCONTENTASSIST() {
ss_ad = SpreadsheetApp.openById(getfid('adminDATA'));
ss_ad.
}

Is there a way to remove a script from a doc (using the new doc embedded script)

I developed a script extension that uses a Google doc as template AND as script holder.
It gives me a very nice environment to implement a mail merge application (see below).
At some point I use the DocsList class makeCopy(new Name) to generate all the docs that will be modified and sent. It goes simply like that :
var docId=docById.makeCopy('doc_'+Utilities.formatString("%03d",d)).getId();
Everything works quite nicely but (of course) each copy of the template doc contains a copy of the script which is obviously not necessary ! It is also a bit annoying since each time I open a copy to check if data are right I get the sidebar menu that opens automatically which is a time consuming process ...
My question is (are) :
is there any way to remove the embedded script from the copy ? (that would be simple)
or should I copy all the doc elements from the template to an empty document ? (which is also a possible way to go but I didn't try and I don't know what will be in this doc in real life use...
Shall I get a perfect clone in any case ?)
I've read the doc and didn't find any relevant clue but who knows ? maybe I missed something obvious ;-)
below is a reduced screen capture to show the context of this question :
Following Henrique's suggestion I used a workaround that prevents the UI to load on newly created documents... (thanks Henrique, that was smart ;-)
The function that is called by onOpen now goes like that :
function showFields() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var find = body.findText('#'); // the new docs have no field markers anymore.
if(find != null){ // show the UI only if markers are present in the document.
var html = HtmlService.createHtmlOutputFromFile('index')
.setTitle("Outils de l'option Publipostage").setWidth(370);
ui.showSidebar(html);
}
}

Client validation of multiple widgets

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.

Why is e.parameter.source undefined?

I am trying to find out where a callback function came from, but e.parameter.source has been undefined.
The code I'me using to create the callback event is:
var temp_handler = app.createServerHandler("do_things");
container.add(app.createButton(s_list[i][2]).setId("goto_"+s_list[i][1]).addClickHandler(temp_handler));
container.add(app.createLabel("goto_"+s_list[i][1]));
where container is later added to the app.
The first part of the function that gets called is:
function do_things (e)
{
var app = UiApp.getActiveApplication();
Logger.log(e.parameter);
var src = e.parameter.source;
From this, I have been able to tell that e.parameter is:
{clientY=61, clientX=38, button=1, alt=false, eventType=click, screenY=278, ctrl=false, screenX=493, y=11, shift=false, meta=false, x=34}
This does not include source. I find this peculiar because as far as I can tell, other callback functions in the same file have been able to access and use e.parameter.source without issue.
Does anyone know what I am doing wrong in this callback such that the source parameter is inaccessible?
The other answers do not make much sense to me.
First, because the source parameter is filled by the element id that generated the event, not its name.
Also it's filled automatically, there's no need to addCallbackElement, which is required for accessing widgets contents by their name. And last, set a name for a label is only useful when you're setting a tag on it, as there's no "content" for a label.
All that said, the only problem I can imagine is if you're setting the same id on another widget and it's messing with your original one (the button). But I haven't tested that to be sure.
You simply forgot to give a name to your Label widget. The value returned by the e.parameter is assigned to a widget by its name.
The ID is used to access the widget from outside the UiApp creation function when you need to modify it.
In addition to what Serge answered, you might want to supply a callback element on the handler using
ServerHandler.addCallbackElement()

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.