I wrote a very simple function in Google App Script:
function addProduct(number) {
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow([number, number+1])
}
and tried to run it in Google Spreadsheets by entering =addProduct() in a cell. However, the Google Spreadsheets gave me an error that I don't have the permission to call the function appendRow().
Why is that?
Every custom function must return a value to display.
A custom function cannot affect cells other than those it returns a value to. In other words, a custom function cannot edit arbitrary cells, only the cells it is called from and their adjacent cells. To edit arbitrary cells, use a custom menu to run a function instead. Taken from: Google Developers
Related
Looking to get the name of the newly added sheet (manually added) using an installable trigger.
ChangeType with INSERT_GRID worked fine for me (tested by outputting some random value when sheet gets added) but when I try getting the name of the newly added sheet, it instead gives me the value of the FIRST sheet's name in the document.
Any alternatives? Have heard that this might be a previous bug in App Script.
function log(e)
{
if (e.changeType === "INSERT_GRID") {
var news = e.source.getActiveSheet().getName(); //fails to get correct value
e.source.getSheetByName("Client").getRange(1, 1).setValue(news);
}}
I believe your goal as follows.
You want to detect the sheet name of added sheet using the OnChange event trigger.
In your current issue, you cannot retrieve the sheet name of added sheet. When this is the bug or specification, you want to know about the alternatives for achieving your goal.
For this, how about this answer?
Issue and workaround:
Unfortunately, in the current stage, it seems that the event object of OnChange event trigger has not information about the added sheet. By this, when your script is used, the sheet name of the 1st tab is retrieved. I'm not sure whether this is the bug or the current specification.
So in order to achieve your goal, I would like to propose the following workaround. The flow of this workaround is as follows.
At first, the initial sheet names are saved to PropertiesService.
When the sheet is added, the aded sheet is retrieved by comparing the current sheets and initial sheets retrieved from the PropertiesService.
Sample script:
In order to use this script, please close the Google Spreadsheet and open it again. By this, onOpen is run and the initial sheet names are saved to the PropertiesService. Then, when the sheet is added, the added sheet is retrieved and the sheet name of the added sheet is put to the cell.
function saveCurrentSheets(prop, spreadsheet) {
const sheets = spreadsheet.getSheets().reduce((o, s) => Object.assign(o, {[s.getSheetName()]: true}), {});
prop.setProperty("sheets", JSON.stringify(sheets));
}
function onOpen(e) {
saveCurrentSheets(PropertiesService.getScriptProperties(), e.source);
}
function log(e) {
if (e.changeType === "INSERT_GRID") {
const prop = PropertiesService.getScriptProperties();
const oldSheets = prop.getProperty("sheets");
if (!oldSheets) {
saveCurrentSheets(prop, e.source);
return;
}
const oldSheetsObj = JSON.parse(prop.getProperty("sheets"));
const addedSheet = e.source.getSheets().filter(s => !oldSheetsObj[s.getSheetName()])[0];
const news = addedSheet.getSheetName();
e.source.getSheetByName("Client").getRange(1, 1).setValue(news);
saveCurrentSheets(prop, e.source);
} else if (e.changeType === "REMOVE_GRID") {
saveCurrentSheets(PropertiesService.getScriptProperties(), e.source);
}
}
In this script, please install the OnChange event trigger to the function of log.
In this script, when the existing sheet is deleted, the values of PropertiesService are updated by the current sheet names.
Note:
When there are a lot of sheets in the Google Spreadsheet, I think that the initial sheet names are required to be a file instead of the PropertiesService, because of "Properties value size is 9kB / val." and "Properties total storage is 500kB / property store.". Please be careful this.
References:
Event object of OnChange
Class Properties
Quotas for Google Services
That's the normal behavior. It always returns e.source.getSheets()[0]; Which is the same thing if you get the active sheet when you openById();
Is it possible to have triggers that work for 2 spreadsheets (2, independent files) with one script? I am stuck at how to do this part, as it needs to be automated. So far in my research I have seen importrange triggers but nothing that can do a prompt on a specific worksheet.
My goal is to make a "list veto" function with the following requirements:
Must use google spreadsheets
a) One Sheet with "triggers" that is the "controller" or moderator
b) One Sheet for the "preferred" elector
c) One Sheet for the "secondary" elector
The list will be dynamic, but for this example let us say it consists of a list of 15 locations.
Both "electors" will have to veto continuously until there is only 8 locations left.
The "controller" will trigger a system that asks the "preferred elector" first, then the "secondary," by a prompt that goes to their respective spreadsheet. Once the preferred makes a choice, the list of locations is deducted, then a prompt goes to the worksheet of the "secondary."
Triggers are set by spreadsheet, not by sheet, but you could use JavaScript control statements like if..else and switch to control when should be executed parts of your script.
Example:
function respondToOnEdit(e){
var sheetName = e.source.getActiveSheet().getName();
if(sheetName === 'Sheet1'){
// do something when Sheet1 is edited
} else if(sheetName === 'Sheet2'){
// do something when Sheet2 is edited
} else {
// do something when any other sheet is edited
}
}
You will have to use code to create an installable trigger for each spreadsheet.
Code snippet:
[id1, id2].forEach(id => {
var spreadsheet = SpreadsheetApp.openById(id);
ScriptApp.newTrigger('respondToOnEdit')
.forSpreadsheet(spreadsheet)
.create()
}
Related
Is there any way to automatically create installable triggers?
I am making and app that sends emails to different people accordingly to a google sheet. Now I am struggling with the automatization of the process.
What I want to do is create a trigger that sends the email after a new row is added to the sheet and I know it could be done using an event object.
Could you guys help me? Thanks.
You can use this sort of script if the change is a user inserting a row into a given sheet.
function onAChange(e) {
//console.log(JSON.stringify(e));
if(e.changeType=="INSERT_ROW" && e.source.getActiveSheet().getName()=="Your Sheet Name") {
sendEmail()
}
}
The event object looks like this:
{"authMode":"","changeType":"INSERT_ROW","source":{},"triggerUid":"","user":{"email":"","nickname":""}}
on Change Event Object
Looking to get the name of the newly added sheet (manually added) using an installable trigger.
ChangeType with INSERT_GRID worked fine for me (tested by outputting some random value when sheet gets added) but when I try getting the name of the newly added sheet, it instead gives me the value of the FIRST sheet's name in the document.
Any alternatives? Have heard that this might be a previous bug in App Script.
function log(e)
{
if (e.changeType === "INSERT_GRID") {
var news = e.source.getActiveSheet().getName(); //fails to get correct value
e.source.getSheetByName("Client").getRange(1, 1).setValue(news);
}}
I believe your goal as follows.
You want to detect the sheet name of added sheet using the OnChange event trigger.
In your current issue, you cannot retrieve the sheet name of added sheet. When this is the bug or specification, you want to know about the alternatives for achieving your goal.
For this, how about this answer?
Issue and workaround:
Unfortunately, in the current stage, it seems that the event object of OnChange event trigger has not information about the added sheet. By this, when your script is used, the sheet name of the 1st tab is retrieved. I'm not sure whether this is the bug or the current specification.
So in order to achieve your goal, I would like to propose the following workaround. The flow of this workaround is as follows.
At first, the initial sheet names are saved to PropertiesService.
When the sheet is added, the aded sheet is retrieved by comparing the current sheets and initial sheets retrieved from the PropertiesService.
Sample script:
In order to use this script, please close the Google Spreadsheet and open it again. By this, onOpen is run and the initial sheet names are saved to the PropertiesService. Then, when the sheet is added, the added sheet is retrieved and the sheet name of the added sheet is put to the cell.
function saveCurrentSheets(prop, spreadsheet) {
const sheets = spreadsheet.getSheets().reduce((o, s) => Object.assign(o, {[s.getSheetName()]: true}), {});
prop.setProperty("sheets", JSON.stringify(sheets));
}
function onOpen(e) {
saveCurrentSheets(PropertiesService.getScriptProperties(), e.source);
}
function log(e) {
if (e.changeType === "INSERT_GRID") {
const prop = PropertiesService.getScriptProperties();
const oldSheets = prop.getProperty("sheets");
if (!oldSheets) {
saveCurrentSheets(prop, e.source);
return;
}
const oldSheetsObj = JSON.parse(prop.getProperty("sheets"));
const addedSheet = e.source.getSheets().filter(s => !oldSheetsObj[s.getSheetName()])[0];
const news = addedSheet.getSheetName();
e.source.getSheetByName("Client").getRange(1, 1).setValue(news);
saveCurrentSheets(prop, e.source);
} else if (e.changeType === "REMOVE_GRID") {
saveCurrentSheets(PropertiesService.getScriptProperties(), e.source);
}
}
In this script, please install the OnChange event trigger to the function of log.
In this script, when the existing sheet is deleted, the values of PropertiesService are updated by the current sheet names.
Note:
When there are a lot of sheets in the Google Spreadsheet, I think that the initial sheet names are required to be a file instead of the PropertiesService, because of "Properties value size is 9kB / val." and "Properties total storage is 500kB / property store.". Please be careful this.
References:
Event object of OnChange
Class Properties
Quotas for Google Services
That's the normal behavior. It always returns e.source.getSheets()[0]; Which is the same thing if you get the active sheet when you openById();
On the advice of someone below, I am editing this post:
My initial goal is that when my Google sheet is opened, and every time it is opened, I would like the values of several Data Validation dropdown menus, currently located in Cells A10, A15, and A20, to be set to the option of "Select" -- which is a word in the validation range, along with 2 other text values.
I have been informed that this needs to be declared at a global scope -- but I am a complete script novice and, frankly, have no idea as to how to make this work.
Any advice will be greatly appreciated.
function onOpen() {
SpreadsheetApp.getActiveSheet().getRange('A10').setValue('Select');
SpreadsheetApp.getActiveSheet().getRange('A15').setValue('Select');
SpreadsheetApp.getActiveSheet().getRange('A20').setValue('Select');
Note for new readers:
The original code on the question was this
//When the sheet is opened, the contents of Cell A2 are cleared and the values in the Data Validation dropdown menus in Cells A10, A15, and A20 are set to the default "Select"
function myFunction() {
function onOpen() {
SpreadsheetApp.getActiveSheet().getRange('A2').clearContent();
SpreadsheetApp.getActiveSheet().getRange('A10').setValue('Select');
SpreadsheetApp.getActiveSheet().getRange('A15').setValue('Select');
SpreadsheetApp.getActiveSheet().getRange('A20').setValue('Select');
}
//When the contents of Cell A2 are edited (changed), the values in the Data Validation dropdown menus in Cells A10, A15, and A20 are set to the default "Select"
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var cell = sheet.getRange('A2')
var cellContent = cell.getValue()
if(cellContent === (edit) {
SpreadsheetApp.getActiveSheet().getRange('A10').setValue('Select');
SpreadsheetApp.getActiveSheet().getRange('A15').setValue('Select');
SpreadsheetApp.getActiveSheet().getRange('A20').setValue('Select');
}
}
Simple triggers should not be declared as local functions of another function, they should be declared at the global scope.
In other words, don't put onOpen and onEdit inside of myFunction.
A function on the global scope in a Google Apps Script script looks like this:
NOTE: Only one code line is included inside onOpen code block {} for simplicity. It could have any number of code lines that takes no more than 30 seconds to execute.
By the other hand simple triggers has several limitations so maybe instead of simple triggers you should consider to use installable triggers. To learn about Google Apps Script triggers please read https://developers.google.com/apps-script/guides/triggers
Also, you should bear in mind the real-time collaboration features of Google Sheets. If one user has opened the spreadsheet and another user open the same spreadsheet, the onOpen, simple and installable triggers, will be triggered and could change what the first user already edited.
After some testing I was able to make it work. As Ruben said, onOpen, onEdit cannot be inside any other function. These specify already an action so when an action of onOpen or onEdit the script will be running automatically when the spreadsheet is opened or edited.
You can check more information about Apps Script Triggers https://developers.google.com/apps-script/guides/triggers but in this specific case onEdit will run every time any cell is updated unless you specify the cell you want to run the script by using Event Objects for more information check https://developers.google.com/apps-script/guides/triggers/events.
For this specific scenario since you want the script to run when the cell is updated you have to check if that cell is being updated by using e.range.getA1Notation() the getA1Notation() returns the range in A1 notation for more information check https://developers.google.com/apps-script/reference/spreadsheet/range#getA1Notation() there is an example that will make you understand the following script.
function onOpen(e){
var app = SpreadsheetApp.getActiveSpreadsheet();
var sheet = app.getActiveSheet();
sheet.getRange("A2:A2").clearContent();
sheet.getRange("A10:A10").setValue("Select");
sheet.getRange("A15:A15").setValue("Select");
sheet.getRange("A20:A20").setValue("Select");
}
function onEdit(e){
var app = SpreadsheetApp.getActiveSpreadsheet();
var sheet = app.getActiveSheet();
var A2 = sheet.getRange("A2:A2");
console.log(e.range.getA1Notation());
if (e.range.getA1Notation() === "A2"){
console.log("A2 Updated");
sheet.getRange("A10:A10").setValue('Select');
sheet.getRange("A15:A15").setValue('Select');
sheet.getRange("A20:A20").setValue('Select');
}
}
the onOpen function will be running every time the spreadsheet is opened, and the onEdit function will be executed every time the cell "A2" changes its value. Basically you are checking if the range with A1 Notation is equals to "A2" then it will run the code within the if statement, otherwise the function will be executed but will take no action since the cell A2 is still without any change. By doing this I am not sure if there is a limit of this kind of executions but that is something you can investigate further, I don't think it will affect since this should be running on the client side not on a server side.
I hope that helps, greetings.