Google Sheets Add-On onOpen() only running once but onEdit() is fine - google-apps-script

I'm working on an Add-On for Sheets called LastEdit. The problem I'm having is that onOpen() only runs for the first time the Add-On is installed:
The code works fine running as a bound script to the original Sheet where I write the Add-on; issues only arrive with the published Add-on (installed to a different Google Drive account)
If I innstall the Add-on from the Chrome Web Store:
A new untitled Sheet is automatically opened with the Add-On installed.
Everything works just fine this first time: help tip pops up, AddonMenu populates, etc.
If I refresh the page onOpen() stops working. I still see the Add-on menu called LastEdit but it's empty except for the default Help button; all my custom menu items are missing.
If I open a new Sheet I have the same issue.
If I go to Add-ons -> Manage add-ons and click Manage -> Use in this document there is no change. I can check, uncheck, and refresh the page in any combination and no change.
The script is otherwise working fine: onEdit() is getting called no problem.
I snuck a ui.alert() at the start of onOpen(); I only see this alert once with the automatically opened first Sheet.
I tried removing all code from onOpen() except for ui.alert() and it can't even manage that.
So, why won't onOpen() run anymore??
Here's the code BTW:
var documentProperties = PropertiesService.getDocumentProperties();
var ui = SpreadsheetApp.getUi();
var sheet = SpreadsheetApp.getActive();
var editCell;
// This function runs whenever cells are edited
function onEdit() {
updateLastEdit();
}
// This function updates the editCell contents
function updateLastEdit() {
// Fetch the coordinate of the designated LastEdit cell
editCell = documentProperties.getProperty('editCell');
// If our docProp editCell is 0 then we don't have a LastEdit to update
if (editCell != 0.0) {
sheet.getRange(editCell).setValue(new Date() + '');
}
}
// This function will be used to designate the new LastEdit cell
function lastEdit() {
editCell = sheet.getActiveCell()
SpreadsheetApp.getUi().alert("LastEdit cell added at: " + editCell.getA1Notation());
documentProperties.setProperty('editCell', editCell.getA1Notation());
// Once we've stashed the location of the LastEdit cell we move to update the LastEdit cell contents
updateLastEdit();
}
// When the Sheet opens add a new custom menu
function onOpen(e) {
// This is the alert for testing purposes
ui.alert("onOpen() has run!");
// Contingency strategy; set an Installable Trigger to perform the onOpen tasks
//ScriptApp.newTrigger("openTrigger").forSpreadsheet(sheet).onOpen().create();
// Creating custom menu for this app
newMenu = ui.createAddonMenu();
newMenu.addItem('Insert LastEdit Cell', 'insertLastEdit');
newMenu.addItem('Disable LastEdit Cell', 'disableLastEdit');
newMenu.addItem('LastEdit Cell Location', 'locateLastEdit');
newMenu.addSeparator();
newMenu.addItem('About', 'aboutLastEdit');
newMenu.addToUi();
// I know this can be shortened, but I removed/tested each individual
// item to see if any of these were derailing onOpen()
}
// Insert LastEdit Cell
function insertLastEdit() {
lastEdit();
}
// Disable LastEdit Cell
function disableLastEdit() {
documentProperties.setProperty('editCell', 0);
SpreadsheetApp.getUi().alert("LastEdit cell disabled");
}
// Fetch and display the LastEdit cell location via popup
function locateLastEdit() {
editCell = documentProperties.getProperty('editCell');
if (editCell != 0) {
SpreadsheetApp.getUi().alert("LastEdit cell is located at: " + editCell);
} else {
SpreadsheetApp.getUi().alert("No LastEdit cell is active ");
}
}
// About!
function aboutLastEdit() {
ui.alert("About LastEdit", SpreadsheetApp.getUi().ButtonSet.OK);
}
// After installation just run the onOpen function
function onInstall(e) {
onOpen(e);
}
// If an Installable Trigger is required...
function openTrigger() {
// I had a duplicate of onOpen() in here, but have abandoned this strategy
}
What am I missing here? There seem to be a lot of moving parts to the Add-on authorization (Bound scripts, Simple Triggers, Enabled in Document, authMode.LIMITED vs. FULL, etc.) I reviewed the Add-on Authorization Lifecycle page but it seems to point to this process being handled largely on an automatic basis.
Thanks in advance for all your help!

"If an add-on is installed for a user but not enabled in the current document, onOpen(e) runs in AuthMode.NONE; if the add-on is enabled in the current document, onOpen(e) runs in AuthMode.LIMITED. If the add-on is both installed and enabled, the enabled state takes precedence, since LIMITED allows access to more Apps Script services."
When the script is on AuthMode.NONE it does not have access to the Properties Service. Since you have global code that calls this service, the add-on if failing the execution of onOpen().
You need to move this global code to within a function.
See more here https://developers.google.com/apps-script/add-ons/lifecycle#authorization_modes

Related

installing a trigger by script keeps adding triggers to template sheet

I have ran into a problem with a template I have been working on. I have a template file in a folder on a shared drive that has a script containing a create function and close function, triggered by this installed on edit ran from a drop down.
function onMyEdit(e) {
if(sh.getName()=="Sheet2" && e.range.columnStart==6 && e.range.rowStart==3 && e.value) {
switch(e.value) {
case 'Create':
Create();
break;
case 'Close':
Close();
}
e.range.setValue('');
}
}
The create function created a renamed copy of the template in a folder on the shared drive, adds a calendar event, and writes certain data to another spreadsheet in the drive acting as a data base. I manually installed a trigger for the onMyEdit function in the template file, but when I create the copy the trigger does not work so the close function will not run from the newly created sheet. I tried programming the create function to install a trigger on the new sheet. Useing this
var WOssID = WOss.getId(); //get the ID of the newly created work order spreadsheet
Logger.log(WOssID)
ScriptApp.newTrigger('onMyEdit')
.forSpreadsheet(WOssID)
.onEdit()
.create()
where WOss is the spreadsheet that I just created by copying the template, and I check the logger and the ID does match the new sheet. This does fix it so I can run the close function from the newly created sheet, but it seems to add a trigger to the template sheet not the new sheet, and it keeps adding a trigger every time I run the create function from the template file until it limits out at 20 and then it all breaks. I must be misunderstanding something because I don't understand why it is adding triggers to the template sheet when I have the ID for the newly created sheet being called. Also hoping to get this all to run on an Ipad so believe that means I can't use added on menus, onopen calls, or clickable buttons. Any help is appreciated.
UPDATE
So I now have a simple onEdit trigger that creates a installed onMyEdit trigger to my work order sheet on first edit and doesn't create multiple triggers in my template file. However I need to figure out a way to authorize it in the newly created sheet, without going into the script editor. If I open the newly created sheet and make an edit the new installed trigger shows up in the project triggers, but it won't run until I try to run it from the script editor first at which time it asks for authorization. Is there a way to tie the authorization to a checkbox that when clicked makes the popup appear to authorize the installed trigger so it can run?
function onEdit(){
var WOss = SpreadsheetApp.getActiveSpreadsheet();
var WOssID = WOss.getId();
var allTriggers =
ScriptApp.getUserTriggers(WOss)//ScriptApp.getProjectTriggers();
Logger.log(allTriggers.length)
if(allTriggers.length == '0' ){
ScriptApp.newTrigger('onMyEdit').forSpreadsheet(WOssID).onEdit().create()
}
else(allTriggers.length != '0')
{}
}
Here's a function I use to create triggers:
function createOnFormSubmitTriggerForSpreadsheet() {
const ssid=SpreadsheetApp.getActive().getId();
const resp=SpreadsheetApp.getUi().prompt("Create On Form Submit Trigger", "Enter Function Name", SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
if(resp.getSelectedButton()==SpreadsheetApp.getUi().Button.OK) {
let funcname=resp.getResponseText();
The next line prevents me from creating more than one trigger for the same function
if(!isTrigger(funcname)) {//I use this to keep from creating more than one trigger
I recommend that you do the same
ScriptApp.newTrigger(funcname).forSpreadsheet(ssid).onFormSubmit().create();
}
}
}
function isTrigger(funcName){
var r=false;
if(funcName){
var allTriggers=ScriptApp.getProjectTriggers();
for (let i=0;i<allTriggers.length;i++){
if(funcName==allTriggers[i].getHandlerFunction()){
r=true;
break;
}
}
}
return r;
}
And don't mean to be cruel but you code is a mess and you dont' need to describe your whole project we just want to see the minimum code that generates the problem. You said that you didn't know what's important and what's not. Well when you spend the time to create a minimum reproducible example you will have learned a lot more about your code and I often find that I end up solving the problem on my own.

My Script works for some people but not others

I created an audit template in Google sheets that uses several scripts. The script is designed to populate a cell with the active user's email and a time date stamp when they click on a check box. The script works, except for one person, the script does not recognize his active user email. Can someone tell me why the script works for some but not others and how I might fix it?
I've tested the script in a blank test spreadsheet and it works for this person. We had this same issue in another Google Sheet, but we remedied it my creating a new spreadsheet and copying all of the tabs and script. However, attempts to do the same with this spreadsheet have failed.
Here's my script:
function onEdit(e) { //this is the event Google Sheets fires when cells are changed
var email = Session.getActiveUser().getEmail(); // Get the email address of the person running the script.
var date = new Date();
var spreadsheet = SpreadsheetApp.getActive();
if (e.source.getActiveSheet().getName() == "InterimTestsOfControls") { //only do this if the changed cell is on the specific worksheet you care about
switch (e.range.getA1Notation()) { //This gets the cell that was edited
case "E5": //and this switch statement allows you to only respond to the cells you care about
if (e.value == "TRUE") {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Prepared by " + email + " " + date);
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
else {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Click box to sign-off as preparer");
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue("");
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
}
}
}
From the official Session class documentation:
getActiveUser()
Gets information about the current user. If security policies do not allow access to the user's identity, User.getEmail() returns a blank string.
This situation may happen under many, different circumstances:
The user's email address is not available in any context that allows a script to run without that user's authorisation, like a simple onOpen(e) or onEdit(e) trigger, a custom function in Google Sheets, or a web app deployed to "execute as me" (that is, authorized by the developer instead of the user).
These restrictions generally do not apply if the developer runs the script themselves or belongs to the same G Suite domain as the user.
In your situation I see that you are using a simple onEdit(e) trigger - so it looks like this may be the issue. You may want to check out Installable triggers instead.
function onEdit(e) {
var sh=e.range.getSheet();
var email = Session.getActiveUser().getEmail(); //This is a permissions issue. You may have to use an installable trigger and even then it's not guaranteed
var date = new Date();//you might want to use Utilities.formatDate()
//var spreadsheet = SpreadsheetApp.getActive();this is than same as e.source
if (sh.getName()=="InterimTestsOfControls") {
//you can selection your cells with information that's already in the event block rather than having to run another function
if(e.range.columnStart==6 && e.range.rowStart==5) {
if (e.value=="TRUE") {
sh.getRange("F5").setValue("Prepared by " + email + " " + date);
e.source.getSheetByName('Audit Planning').getRange('C7').setValue(email);
} else {
e.source.getRange("F5").setValue("Click box to sign-off as preparer");
e.source.getSheetByName('Audit Planning').getRange('C7').setValue("");
}
}
}
}
Getting and setting the active range is a remnant of the Macro Recorder following your every step and in general is not required in your scripts. And in fact it will slow down your scripts so please try to avoid using that approach in general for most scripting.

Google app script's Session.getActiveUser() not working for cross domain users

I'am using Google App Script and Google Spreadsheet onOpen() trigger. I'm locking logged in user in Google Spreadsheet.
The restriction for the same is that it's not logging email id on cross domain which I found on forums.
Here is the code for the same:
function onOpen(e)
{
try
{
var ui = SpreadsheetApp.getUi();
ui.createMenu('Action')
.addItem('Add Data', 'addData')
.addToUi()
var sheet =SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Login Details");
sheet.appendRow([Session.getActiveUser().getEmail(),new Date()]);
}
catch(e)
{
Logger.log(e);
}
}
One observation from onOpen() functionality :
1] I'm owner of spreadsheet and it only logs the email ids of our company domain. (Automatically onOpen() of spreadsheet)
2] From other domain when I open bounded script of spreadsheet and run onOpen() then it logs the logged in user.
What is reason behind the same and is there any workaround for user on cross domain?
I think Session.getActiveUser().getEmail() has issues with onOpen trigger on outside domain when that person is not the owner.
Also, automatic per minute triggers also are not able to log active user.
Here's the workaround:
Add menu in the spreadsheet itself, with an option which when clicked, will run the function to append the row that you want. As, Session.getActiveUser().getEmail() is working when we run function manually from script.
Use this code:
function addMenu()
{
var ui = SpreadsheetApp.getUi();
ui.createMenu('Log user')
.addItem('Run script', 'appendRow')
.addToUi()
}
And also add this function:
function appendRow()
{
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("YOUR_SHEET").appendRow([Session.getActiveUser().getEmail(),new Date()]);
}
Now, whenever you want to log the active user, go to that Log user and click Run script and it will append the row.
Also, don't forget to set onOpen trigger for addMenu() function on client side or even your side.
For more details, we can read this: Class session

Google Scripts Sheets hide/ unhide

In writing a 'custom function' Google Script for my particular sheet, I simply want to hide a column:
function hideColumn(index) {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get first sheet
var sheet = ss.getSheets()[0];
sheet.hideColumns(index);
}
This code works fine when I run it from within the Script editor but if I try to run it from inside a cell "=hideColumn(2)", I get the following error:
"You do not have permission to call hideColumns (line 48)."
From the same sheet/ script I'm able to run other custom functions such as:
function metersToMiles(meters) {
if (typeof meters != 'number') {
return null;
}
return meters / 1000 * 0.621371;
}
This seems to be some issue with the hideColumns function being run from inside a sheet? (ie. custom function?)
Your script 'hideColumn' is not a custom function, but a 'normal script'. Also it does not return anything (whereas the second function does). Only custom functions can be entered like formulas in the spreadsheet. See here for more info. My advice would be to create an extra menu-item using an onOpen trigger so you can run the function from the (spreadsheet)menu.
Hope that helps ?

Allow other users to execute google spreadsheet scripts

I have made a simple Google Spreadsheet with some scripting behind to update colours depending on status, set update and creation dates for rows and a few other controls.
It all works when I am editing it with my own user, but now that set spreadsheet is set to public (access via link), anyone else accessing the spreadsheet gets an error you do not have permissions to do this action.
I am using onEdit() triggers and apparently guest users do not have permission to execute them. Do I have to configure something in order for it to work?
thanks
Not a real solution but somme observations:
I made a little script and tested it on the old ad the new spreadsheet type:
function imwatchingus(event) {
Logger.log(JSON.stringify(event));
try{
if(event.value=="faux"){
SpreadsheetApp.getActiveRange().setBackground("red");
}
else if(event.value=="vrai"){
SpreadsheetApp.getActiveRange().setBackground("green");
}
else{
SpreadsheetApp.getActiveRange().setBackground("blue");
}
}
catch(e){}
}
function spyonme(){
var trigs = ScriptApp.getProjectTriggers();
var func = [];
for (var i in trigs){
func.push(trigs[i].getHandlerFunction());
}
if(func.indexOf("imwatchingus")>-1){
return("already enrolled");
}
else{
ScriptApp.newTrigger("imwatchingus").forSpreadsheet(SpreadsheetApp.getActive().getId()).onEdit().create();
return("enrolling you");
}
}
the function imwatchingus is the one that will do the job (it colors the background of the cell in red / green / blue depending on his text).
to have this function working I added a trigger to it. You can do that with the menu or launching the function spyonme.
On the New spreadsheet there is an issue. Only the first cell (A1) will be modified, but you can use this script if you are anonymous.
On the old spreadsheet, the color is applyed on the changed cell, but it's not working in anonymous mode.