I am running a script in google sheets script editor, to trigger the script I use onChange in the Current project's triggers, using specific function -> From Spreadsheet -> On change.
Important to mention that I use the sheet with 3 more members that have edit permission who change time to time the data, I built the script and activate the trigger.
When I change anything in the sheet I get alerted (the trigger were on) but when other member changes the data the trigger doesn't work.
What have I missed here?
Thanks.
Everyone needs a trigger
You can add the trigger management into the program so that they have the triggers in their projects too. The isTrigger() function insures that you only create one trigger for each instance. You find the documentation here.
function myTriggerSetup()
{
if(!isTrigger('functionName'))
{
ScriptApp.newTrigger('functionName').forSpreadsheet('Spreadsheet').onChange().create();
}
}
function isTrigger(funcName)
{
var r=false;
if(funcName)
{
var allTriggers=ScriptApp.getProjectTriggers();
var allHandlers=[];
for(var i=0;i<allTriggers.length;i++)
{
allHandlers.push(allTriggers[i].getHandlerFunction());
}
if(allHandlers.indexOf(funcName)>-1)
{
r=true;
}
}
return r;
}
Related
How can I prevent any anonymous editor from adding a new sheet inside the workbook. I wish to allow them to edit just one single sheet, but some editors mistakenly mess up the workbook by inserting unwanted sheets. thanks in advance. i tried the script in this link below but it does not seem to work.
Prevent users from creating new sheets in a shared google spreadsheet
// Deletes any tab named "SheetN" where N is a number
function DeleteNewSheets() {
var newSheetName = /^Sheet[\d]+$/
var ssdoc = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ssdoc.getSheets();
// is the change made by the owner ?
if (Session.getActiveUser().getEmail() == ssdoc.getOwner().getEmail()) {
return;
}
// if not the owner, delete all unauthorised sheets
for (var i = 0; i < sheets.length; i++) {
if (newSheetName.test(sheets[i].getName())) {
ssdoc.deleteSheet(sheets[i])
}
}
}
I created this spreadsheet https://docs.google.com/spreadsheets/d/1EdF8I0tyfQagfw1cxHCWgsr2umXi6-vbu7GvM8SJQSM/edit#gid=0 with the code above and set the triggers, but anyone could still create a new sheet.
I believe your current situation and goal are as follows.
There is a sheet in Google Spreadsheet.
You don't want to make the anonymous users insert a new sheet.
Issue and workaround:
In the current stage, unfortunately, there is no method for clearly detecting the edit of an anonymous user. In your script, I thought that the script can be used for the special permitted users. In this case, the anonymous users cannot be included. I thought that this might be the reason for your issue. So in order to achieve your goal, it is required to prepare a workaround. In this answer, I would like to propose a workaround.
When the sheet insert is detected, OnChange trigger can be used. But, the identification of the user is a bit difficult. When a function installedOnChange(e) is executed by the OnChange trigger, the following condition can be obtained. And, in order to check the active user, Session.getActiveUser().getEmail() and Session.getEffectiveUser().getEmail() are used.
When the owner inserts a new sheet,
e of installedOnChange(e) has the property of "user":{"email":"### owner's email ###","nickname":"### owner name ###"}.
Session.getActiveUser().getEmail() and Session.getEffectiveUser().getEmail() return the owner's email.
When the special permitted user inserts a new sheet,
e of installedOnChange(e) has the property of "user":{"email":"","nickname":""}. In this case, no email address and no name are returned.
Session.getActiveUser().getEmail() returns empty, and Session.getEffectiveUser().getEmail() returns the owner's email.
When the anonymous user inserts a new sheet,
e of installedOnChange(e) has the property of "user":{"email":"### owner's email ###","nickname":"### owner name ###"}.
Session.getActiveUser().getEmail() and Session.getEffectiveUser().getEmail() return the owner's email.
In this case, the situation is the same with the owner. But, in order to identify this, here, the simple trigger is used. Because the anonymous user cannot use the simple trigger. For example, when the anonymous user edits a cell, the simple trigger is not run. This situation is used.
When these conditions are reflected in a sample script, it becomes as follows.
Usage:
1. Prepare sample script.
Please copy and paste the following script to the script editor of Spreadsheet. And, please directly run onOpen function with the script editor. By this, the initial sheet name is stored in PropertiesService. And also, when the Spreadsheet is opened, onOpen is run. By this, the initial condition can be saved.
function onOpen() {
PropertiesService.getScriptProperties().setProperty("sheetName", JSON.stringify(SpreadsheetApp.getActiveSpreadsheet().getSheets().map(s => s.getSheetName())));
}
function onSelectionChange(e) {
CacheService.getScriptCache().put("simpleTrigger", JSON.stringify(e), 30);
}
function deleteSheet(e) {
if (e.changeType != "INSERT_GRID") return;
const sheetNames = JSON.parse(PropertiesService.getScriptProperties().getProperty("sheetName"));
e.source.getSheets().forEach(s => {
if (!sheetNames.includes(s.getSheetName())) e.source.deleteSheet(s);
});
}
function installedOnChange(e) {
const lock = LockService.getDocumentLock();
if (lock.tryLock(350000)) {
try {
Utilities.sleep(3000); // Please increase this wait time when the identification is not correct.
const c = CacheService.getScriptCache();
const simpleTrigger = c.get("simpleTrigger");
const activeUser = Session.getActiveUser().getEmail();
const effectiveUser = Session.getEffectiveUser().getEmail();
if (activeUser && effectiveUser && simpleTrigger) {
// Operation by owner.
// do something.
PropertiesService.getScriptProperties().setProperty("sheetName", JSON.stringify(SpreadsheetApp.getActiveSpreadsheet().getSheets().map(s => s.getSheetName())));
} else if (!activeUser && effectiveUser && simpleTrigger) {
// Operation by permitted user.
// do something.
deleteSheet(e); // If you want to make the permitted user not insert new sheet, please use this line.
} else {
// Operation by anonymous user.
// do something.
deleteSheet(e);
}
c.remove("simpleTrigger");
} catch (e) {
throw new Error(JSON.stringify(e));
} finally {
lock.releaseLock();
}
} else {
throw new Error("timeout");
}
}
In this sample script, the owner, the special permitted user, and the anonymous user are identified. And, the owner can insert a new sheet. But, when the special permitted user and the anonymous user insert a new sheet, the inserted sheet is deleted. By this, as a workaround, your goal might be able to be achieved.
In this script, onSelectionChange is used as a simple trigger for detecting the anonymous user.
3. Install OnChange trigger as an installable trigger.
Please install a trigger to the function installedOnChange as the OnChange installable trigger. Ref
3. Testing.
In order to test this script, please insert a new sheet by the special permitted user and the anonymous users. By this, the inserted sheet is deleted. And, when the owner inserts a new sheet, the inserted sheet is not deleted.
Note:
In this sample script, in order to check whether the simple trigger is executed, Utilities.sleep(3000) is used. So, the time for identifying the user is a bit long. I thought that this might be a limitation.
For example, if you are not required to identify the users who insert a new sheet, you can also use the following simple script. Before you use this script, please install OnChange trigger to installedOnChange and run onOpen with the script editor. In this sample script, all users cannot insert a new sheet.
function onOpen() {
PropertiesService.getScriptProperties().setProperty("sheetName", JSON.stringify(SpreadsheetApp.getActiveSpreadsheet().getSheets().map(s => s.getSheetName())));
}
function installedOnChange(e) {
const lock = LockService.getDocumentLock();
if (lock.tryLock(350000)) {
try {
if (e.changeType != "INSERT_GRID") return;
const sheetNames = JSON.parse(PropertiesService.getScriptProperties().getProperty("sheetName"));
e.source.getSheets().forEach(s => {
if (!sheetNames.includes(s.getSheetName())) e.source.deleteSheet(s);
});
} catch (e) {
throw new Error(JSON.stringify(e));
} finally {
lock.releaseLock();
}
} else {
throw new Error("timeout");
}
}
References:
Installable Triggers
Simple Triggers
getActiveUser()
getEffectiveUser()
how to get current list of sheet names automatically refresh by google app script when making new sheets or changing sheet name or duplicaing sheets or deleting sheets from google spread sheet
:::::: I need list of sheets name ::::::::::::
There are many sheets
New sheet will be added by other user
Name of new sheet will be changed by other user
some sheets will be deleted by other user
I need existing sheets name list not past
::::::::::::::::::::::::::::::::::::::::
and the list of sheet name should display on second sheet that code expression is sheet[1]
below code are working well. but it's not refresh by adding sheets or deleting sheets
function sheetnames()
{
return SpreadsheetApp.getActiveSpreadsheet().getSheets().map(function(x) {return x.getName();});
}
I believe your situation and goal as follows.
You are using the function of sheetnames() as the custom function on Google Spreadsheet.
You have already confirmed that your function of sheetnames() works.
You want to refresh the custom function when the sheet is deleted, inserted, copied and the sheet name is changed.
In order to achieve above, I would like to propose the following method.
Usage:
1. Prepare script.
In this case, the sample script for refreshing the custom function of sheetnames() in the Spreadsheet is run by the OnChange event trigger. For this, please copy and paste the following sample script to the container-bound script of Spreadsheet, and save the script.
function onChange(e) {
var lock = LockService.getDocumentLock();
if (lock.tryLock(10000)) {
try {
const prop = PropertiesService.getScriptProperties();
if ((e.changeType === "OTHER" || e.changeType === "REMOVE_GRID" || e.changeType === "INSERT_GRID") && !prop.getProperty("run")) {
const formula = "=sheetnames"; // <--- Please set the function name of the custom function.
const ss = e.source;
const tempFormula = "=sampleFormula";
ss.createTextFinder("^\\" + formula).matchFormulaText(true).useRegularExpression(true).replaceAllWith(tempFormula);
ss.createTextFinder("^\\" + tempFormula).matchFormulaText(true).useRegularExpression(true).replaceAllWith(formula);
prop.setProperty("run", "done");
} else {
prop.deleteProperty("run");
}
} catch(e) {
throw new Error(e);
} finally {
lock.releaseLock();
}
}
}
In order to avoid the duplicate run of the script, LockService is used.
In order to avoid the infinite loop of the trigger, PropertiesService is used.
2. Install OnChange event trigger.
In order to execute the function of onChange, please install the OnChange event trigger to the function onChange. You can see the method for installing this at this official document.
3. Testing
In order to test above script, after you installed the function onChange as the installable OnChange event trigger, for example, please insert new sheet. By this, you can confirm the custom function sheetnames() is refreshed.
References:
Installable Triggers
Class TextFinder
Lock Service
Class PropertiesService
I have a range of cells in a sheet with a list of dropdown values.
I have setup an installable trigger to use onEdit.
I written simple functions to call different google forms in a sidebar depending on the which value is selected in the dropdown.
Range of cells : J4:P20
Each cell has a dropdown with three options A, B and C
If I choose A, I want to call function A which will load sidebar A
If I choose B, I want to call function B which will load sidebar B etc
The sidebars are created.
I just need a complete example of an onEdit function that will call sidebar A,B or C depending on which value is selected in each of the dropdowns in each cell of the specified range.
This is my poor pseudo attempt.
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var values = sheet.getRange("J4:P20").getValues();
Logger.log(values);
var cellContent = values.getValue()
if cellcontent = SQL {
loadsqlSideBar();
}
else if cellcontent = ORACLE {
loadoracleSideBar();
}
else if cellcontent = IMANIS {
loadimanisSideBar();
}
}
function loadsqlSideBar()
{
var userInterface=HtmlService.createHtmlOutputFromFile('sql');//sidebar for html and formBar for form
SpreadsheetApp.getUi().showSidebar(userInterface);
}
function loadoracleSideBar()
{
var userInterface=HtmlService.createHtmlOutputFromFile('oracle');//sidebar for html and formBar for form
SpreadsheetApp.getUi().showSidebar(userInterface);
}
function loadimanisSideBar()
{
var userInterface=HtmlService.createHtmlOutputFromFile('imanis');//sidebar for html and formBar for form
SpreadsheetApp.getUi().showSidebar(userInterface);
}
function onEdit(e) {
if(e.value=='SQL') loadsqlSideBar();
if(e.value=='ORACLE') loadoracleSideBar();
if(e.value=='IMANIS') loadimanisSideBar();
}
In order for you to solve your issue, you will need to use event objects in order to monitor the edited cell.
Snippet
function onEditTrigger(e) {
var cellEdited = e.range.getValue();
if (cellEdited == "SQL") {
loadsqlSideBar();
}
else if (cellEdited == "ORACLE") {
loadoracleSideBar();
}
else if (cellEdited == "IMANIS") {
loadimanisSideBar();
}
}
function loadsqlSideBar()
{
var userInterface=HtmlService.createHtmlOutputFromFile('sql');//sidebar for html and formBar for form
SpreadsheetApp.getUi().showSidebar(userInterface);
}
function loadoracleSideBar()
{
var userInterface=HtmlService.createHtmlOutputFromFile('oracle');//sidebar for html and formBar for form
SpreadsheetApp.getUi().showSidebar(userInterface);
}
function loadimanisSideBar()
{
var userInterface=HtmlService.createHtmlOutputFromFile('imanis');//sidebar for html and formBar for form
SpreadsheetApp.getUi().showSidebar(userInterface);
}
Explanation
The onEditTrigger function is an installable trigger which means that it will trigger once an edit action is detected. The main difference between an installable and a simple trigger is that they can call services which require authorization and they offer more flexibility. There is no need to specify the range J4:P20 since you will gather the edited cell and you will check if its value is one of the wanted ones.
Therefore, the above script makes use of the e event object in order to detect which cell has been edited and then based on its value, it loads the sidebar.
Installing the Trigger
In order to have an installable trigger, you will need to install it. To do so, you will need to go to your project's triggers by clicking this icon.
Afterwards, you will need to create a new trigger with the following options:
Note
Also don't forget to use the correct way for an if structure.
Reference
Event Objects Apps Script;
Installable Triggers Apps Script;
if...else Structure JavaScript.
I am still very new to addons and I am having trouble to install triggers and have the related functions to run.
Below is the function to add 1 "on open" trigger and 1 "on edit" trigger to the sheet.
function addTriggers() {
var sheet = SpreadsheetApp.getActiveSheet();
var triggers = ScriptApp.getUserTriggers(sheet);
if(triggers.length!=2)//
{
ScriptApp.newTrigger('sheetOpen')
.forSpreadsheet(sheet)
.onEdit()
.create();
ScriptApp.newTrigger('sheetEdited')
.forSpreadsheet(sheet)
.onOpen()
.create();
}
Then I tried to install this function through onInstall();
function onInstall(e){
addSpreadsheetEditTrigger();
sheetOpen();
}
function sheetOpen()
{
//do something after the sheet is open;
}
function sheetEdited()
{
//do something when the sheet is edited by user;
}
When I tested this addon, the triggers were not installed and thus none happened. Also please note that I need to use installable triggers because I need to access external files.
Could someone let me know where I did wrong?
1. How to build a trigger manually
If you want to build a trigger for a spreadsheet, you need to specify as parameter in forSpreadsheet() the spreadsheet, not the sheet!
So:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
...
ScriptApp.newTrigger('sheetOpen')
.forSpreadsheet(spreadsheet)
.onOpen()
.create();
...
It seems that you got the assignment of the functions 'sheetOpen' and 'sheetEdited' the wrong way around
You should doublecheck either you really need to build the trigger manually. Instead you can call the already existing onOpen(e) trigger (unless you need an installable one).
Sample:
function onInstall(e){
sheetOpen();
}
function onOpen(e){
sheetOpen();
}
function sheetOpen()
{
//do something after the sheet is open;
}
UPDATE
Now, the limitations of Addons won't allow you to install triggers directly. Instead, you can create a custom menu giving the user the option to install the triggers when choosing the respective option.
Sample:
function onInstall(e) {
onOpen(e);
}
function onOpen(e) {
var ui = SpreadsheetApp.getUi();
ui.createMenu('MyAddOn Menu')
.addItem('Please click here to get started', 'addTriggers')
.addToUi();
}
function addTriggers() {
...
}
ERROR : You do not have permission to call ScriptApp.newTrigger. Required permissions: https://www.googleapis.com/auth/script.scriptapp.
Having permission problem to create time based trigger
function onEdit(e){
var sheetName = e.range.getSheet().getName()
if(sheetName == "Config")
{
if(e.range.getRow()==2 && e.range.getColumn()==1){
createSpreadsheetOpenTrigger()
}
}
}
function createSpreadsheetOpenTrigger() {
Logger.log("hello")
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}
For Simple Triggers, just running the script within the App Script Web IDE is enough and should prompt you with an authentication popup: Simple Auth Steps.
However, to programmatically create new Triggers you need to make sure the onEdit Trigger is Installable! This gives you increased permissions to do what you need.
We need to open up the dev console to set up an installable trigger.
We need to create a new trigger tied to our function. For installable triggers, it is best not to use the default simple trigger onEdit() function name.
Done! We should be able to run our functions based on triggers with increased permission scopes.