I'd like a little sanity check if I may.
Can Javascript code in an HtmlOutput window floated over a Google spreadsheet alter variables and call methods in the gs code that created the HtmlOutput in the first place?
I have a Google spreadsheet with a floating form created like this:
someCode.gs
var theForm;
var theSpreadsheet;
function makeForm() {
:
theForm = HtmlService.createTemplateFromFile('aForm').evaluate();
theSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
theSpreadsheet.show(theForm);
:
}
function recordTheForm(jsonFormData) {
:
theForm.clear();
:
}
Attached to a button in the HTML of "aForm.html" I have a function call back into the file "someCode.gs".
aForm.html
:
google.script.run.recordTheForm(jsonTheForm);
:
Am I right that, even though it was instantiated from someCode.gs, the caller of recordTheForm() can have no knowledge of the contents of that originating memory space?
Is there anyway to get it? such as passing a "context" back and forth?
Variables in GAS:
Browser button clicks and other browser events produce a call to GAS. Every call from the browser to GAS causes your GAS code to re-run, re-initializing all variables. This is a problem for global variables. Global variables need to be passed back from the Browser to your GAS code or reloaded from the Database or Script Properties.
Related
Using Google Apps Script I'm trying to create a global variable (e.g. an array) that can be used in multiple functions, but I can't seem to find the answer anywhere and I need it for my Google Spreadsheet to work.
Code:
var infoSheetArray = null;
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Custom Menu')
.addItem('Fetch Info', 'grabInfo')
.addItem('Run Program', 'itemSetup')
.addToUi();
}
function grabInfo() {
var infoSheet = SpreadsheetApp.openByUrl('....');
infoSheetArray = infoSheet.getSheetValues(1, 1, infoSheet.getLastRow(), 10);
}
In your code, infoSheetArray is a variable with global scope, meaning that it is accessible by all code in your program. What isn't so obvious is that your program is running multiple times & in different "machines", and that's why it may appear that infoSheetArray is getting reset.
Each separate execution of a script is done in a new execution instance, and each of those have their own set of "globals". In your example snippet, for example, onOpen() is an automatic trigger function invoked independently when the spreadsheet is opened in the web editor. You have two other functions, grabInfo() and itemSetup() (not shown) that get invoked independently via user menu extensions.
When any of these three functions are invoked independently, an execution instance is created for it, your program is loaded, and then execution begins. All global statements (e.g. var infoSheetArray = null;) are evaluated first, followed by the specifically triggered function. Any changes made to global variables will persist only within the execution instance the change happened in, and only for as long as that instance is required. Once the function completes, the instance is blown away.
If you need to persist values between execution instances, you need to use some storage method such as the Properties Service or an External Database.
In Google apps script documentation, there is a page about Private functions on server side. That should explain that without private functions, the server code is visible from the user browser.
Can anybody explain how you can see such server side functions in a browser ?
Thanks
See : https://developers.google.com/apps-script/guides/html/communication#private_functions
The server code is never visible on the user's browser, only the functions names. Private functions hides those names, but more importantly they remove the ability from the frontend to call them directly.
In other words, private functions allow you to define your backend entry-points, preventing a malicious user to bypass some checks you might have and call your "internal" functions directly.
To showcase how easy it is to see the name and call any non-private backend function, I've put up this example where we inspect the google.script.run object:
function myFunction() {}
function anotherFunction() {}
function privateFunction_() {}
function doGet() {
return HtmlService.createHtmlOutput(
'<p id="output"></p>'+
"<script>var s = ''; for( var prop in google.script.run ) s+=prop+'<br>';"+
"document.getElementById('output').innerHTML = s;</script>"
);
}
Here's this example published:
https://script.google.com/macros/s/AKfycbzk0d03iB1O3vVYVD_U7eONM357iOPlAn7RFxAeZKx34q1Ones/exec
And its source code (same as above):
https://script.google.com/d/1WMY5jWblGl8U84WvVU_mZjHDg-6rGOoOPnKMF6m2bS_V-2g6IChBVDrg/edit
-- to address a question in the comments
The doGet function cannot be made private since its name is fixed/predefined. But that is not really a problem as this function is supposed to be an entry point anyways, and since you expect it to be called from the users' browsers and can do your parameters checks and such accordingly.
My function includes adding a menu and toast to the document. I have verified that the trigger (onOpen) is set as well. It only works when a user goes into Tools, Script Manager, Run. We have too many users with too many backgrounds to expect then to know how to do this. Why isn't it working? (Using Chrome)
function onOpen()
{
var menus = [{name: "Advance in Workflow", functionName:"sendEmail"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu("Auto Advance FG Workflow", menus);
//sheet.toast(Notify/Remind users);
sheet.toast("While you are here we kindly ask that you do not add, modify or remove any columns.","Welcome - " + username,8);
}
Thanks,
I was having the same issue.
I realized, sometimes Google create some kind of cache of the scripts (I'm used to have a "test" script and I usually alter it's content, and, sometimes, the script runs as if I didn't).
So, what I did that solved the onOpen() not working was changing the function name and ading a trigger manually.
Go to "Resources -> Current script's triggers…"
Choose the function to run on open
It worked like a charm here!
Updated Location Information:
or
Then
This is an old post but I just had this problem and find out why it was not working correctly in my case:
I had, at the top of my script file a variable that required some authorisations and that prevented the script to correctly run. I saw that OP called var username = Session.getActiveUser().getUsername(); (that requires authorisations, and it's may be the cause).
eg:
this code won't work:
function onOpen(){
SpreadsheetApp.getUi()
.createMenu("Exportation")
.addItem("Lancer l'exportation", "exportationMenu")
.addToUi();
}
var stConsCons= SpreadsheetApp.openById(sgcid).getSheetByName("Consultant");
but this one will work:
function onOpen(){
SpreadsheetApp.getUi()
.createMenu("Exportation")
.addItem("Lancer l'exportation", "exportationMenu")
.addToUi();
}
function whatever(){
var stConsCons= SpreadsheetApp.openById(sgcid).getSheetByName("Consultant");
...}
It turns out that you need to add the onOpen(e) function to Triggers!
In my case, onOpen wasn't working because I had a variable, outside of a function, opening a sheet with SpreadsheetApp.openById() rather than SpreadsheetApp.getActiveSpreadsheet(). I guess onOpen doesn't work with openById() even if the sheet you are opening is bound to the script. onOpen() won't work with this kind of a variable outside of a function:
var sheet = SpreadsheetApp.openById("1b_PQD...").getSheetByName("demos")
If your script is bound to the sheet, you can solve this problem by using the getActiveSpreadsheet() function. Otherwise, you can solve it by putting your openById() call into a function.
In my case there was a reference error, that while did not stop the script entirely, it did stop the menu from appearing.
I was only able to detect that error after I run a debug on the script.
It looks like the problem may be that "sheet" isn't defined, which is why the toast is failing.
I know this is a really old question, but for any one finding this now, I may have a solution. The onOpen function often runs with the authorization mode none instead of limited when being used as an event trigger. This may cause errors in things that are related to the specific file or user data. For example:
function onOpen(e) {
SpreadsheetApp.getActivePresentation(); //Will error out if permissions are not set to limited.
SpreadsheetApp.getUi(); //This will always run even if the AuthMode is set to NONE
}
Additionally it is worth noting that if you have any variable used or initialized before onOpen(e), basically any global variables that access sensitive info fail if the AuthMode is set to NONE.
var ss = SpreadsheetApp.getActivePresentation(); //bad
var ss;
...
function init() {
ss = SpreadsheetApp.getActivePresentation(); //good because now that the function is already run we should have full permissions
}
Simple triggers silently fail if they lack permission. I ran into this with an onOpen() in a script that initialized File objects of non-bound files. I moved all File object instantiation to menu functions that do have permission.
In other words, this will not work
function onOpen(e) {
...
let file = DriveApp.getFileById(nonBoundFileId);
...
}
but this will work
function menuFunction() {
...
let file = DriveApp.getFileById(nonBoundFileId);
...
}
because menuFunction can be given permission(s) that simple triggers lack.
In this post, I suggested to use the ScriptDB as an intermediate storage for global data of a Container Extension code. I wrote a sample code for my answer but the sample throws the error: You do not have permission to call query (line X) exception in a ScriptDb.getMyDb().query(...); line. I created the following simpler example demonstrating the problem. The code, both getDBSize and getSource functions, is permitted to use the ScriptDB by running it in the editor. The getDBSize function is executed without any problem by pressing the Run button in the Spreadsheet Script Manager Dialog. The getSource function works everywhere.
I published the Spreadsheet for the example - link. It is impossible to share the code for view, but it is possible to output it in a cell, the cell B3 contains exactly bellow code.
How is possible to permit the Spreadsheet Code to have access to the ScriptDB?
function getDBSize() {
var db = ScriptDb.getMyDb();
var result = db.query({});
var count = result.getSize();
return count;
}
function getSource() {
return this.toSource();
}
The problem is that you're trying to run this function as a spreadsheet custom function, and custom functions are way more limited than all examples on the Container Extension page you linked.
But, from a theoretical point of view, custom functions as well as simple event handlers (e.g. onEdit, onOpen), can not access anything that requires the user account or is associated with the user, only "generic" methods. For example, UrlFetchApp, open the current spreadsheet, or read ScriptProperties.
So, in thesis, querying a ScriptDb should be possible, since it's a generic call and has nothing to do with the active user, it's analogous to ScriptProperties. I don't see a workaround that would actually let you query the db, but you could use ScriptProperties instead. You can easily save and retrieve any object you would save on ScriptDb by using JSON.stringify and .parse. Of course, I'm not comparing ScriptDb capabilites with ScriptProperties, it's just a workaround.
Anyway, this seems like a good candidate for an enhancement request on our issue tracker.
I've created a GUI (MyGui) with a flow panel (mainPanel), a text box (textValue) and a button (getETA). All in a sites script.
The idea is that a value is entered in the text box, the button is clicked and the value is then placed in the cell A1 of a spreadsheet.
There is no handler configured in the GUI and I've copy pasted the script to a fresh site, no success though.
The problem I'm having is that all I get in the spreadsheet cell is "undefined".
Also if I run a debug on the clickGetETA function I get the following error message: "Cannot read property "parameter" from undefined"
I'm new to this but my best guess is that the details of the GUI are not being passed on to the clickGetETA function. I've searched what I can but I can't appear to find a solution...
If someone could tell me where I'm going wrong I'd appreciate he help.
function doGet() {
var app= UiApp.createApplication();
app.add(app.loadComponent("MyGui"));
var clickHandler = app.createServerHandler('clickGetETA');
clickHandler.addCallbackElement(app.getElementById('mainPanel'));
app.getElementById('getETA').addClickHandler(clickHandler);
return app;
}
function clickGetETA(e) {
var sheet = SpreadsheetApp.openById('abcdefghjklmnopqrstuvwxyz');
var ss = sheet.getActiveSheet();
var target = ss.setActiveCell("A1");
target.setValue(e.parameter.textValue);
}
Change:
var ss = sheet.getActiveSheet();
var target = ss.setActiveCell("A1");
To :
var ss = sheet.getSheetByName("SheetName");
var target = ss.getRange("A1");
Good examples for this kind of thing are here:
https://developers.google.com/apps-script/guide_user_interfaces
It should be run as a service. i.e. you use the get function.
Notes: the "e" parameter does not exist when you debug. Debugging is a little limited.
ActiveSheet and ActiveCell are designed to be used from a spreadsheet not a service.
A service creates a web page rather than a popup box.
Eddy.
The callback element used in the addCallbackElement may be a single widget or the a panel of your GUI. When passing a panel, all widgets bellow in the panel hierarchy will be included in the parameter. Are you sure your textbox is inside the panel you're adding?
Also, the values of the widgets can be accessed in the handler function using the widget name (that you must set on the GUI builder). Notice the difference between name and id, the id you use to get the widget using getElementById and also to determine the widget source of the events, when you use the same handler for multiple widgets. And the name for accessing the value. Are you sure you named your textbox as textValue?
What fixed it for me was going into the UI editor and making sure I had an ID assigned for form fields and a name. I was missing the name so my e.parameter.fieldName was undefined.