I'm trying to program a spreadsheet so that whenever I add a new entry to it, it copies the data from that cell into another spreadsheet. Here's my code (that doesn't seem to work):
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var target = SpreadsheetApp.openById("*************");
var target_sheet = target.getSheetByName("Dashboard");
var last_row = target_sheet.getLastRow();
target_sheet.insertRowAfter(last_row);
var target_range = target_sheet.getRange("A"+(last_row+1)+":G"+(last_row+1));
e.range.copyTo(target_range);
}
Any ideas what I'm doing wrong?
There are two types of triggers in Apps Scripts - simple triggers and installable triggers.
onEdit() is an example of a simple trigger (the name is implicit). And there are limits to what you can do inside a simple trigger. And that includes opening another spreadsheet.
So, coming to the solution.
Rename your function to something else, say onEdit1() . And set up an installable trigger manually and set it to run each time the spreadsheet is edited.
Hint: Use Resources --> Current project's triggers from the script editor
Related
i have 3 sheets my spreadsheet. Names is sheet1, sheet2, sheet3. And i have a function "onChange()". i install onChange function at triggers. i want run onChange function if only when add new row at sheet2 . But not working properly. if i write sheet1 or sheet2 or sheet3 anything always run my onChange function.
function onChange(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet2");
var sheet2lr = ss.getRange(ss.getLastRow(),1,1,2).getValues();
var sheet1lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1");
sheet1lr.getRange(getLastRow(),1,1,4).setValues(sheet2lr)}
I believe your goal as follows.
You want to run the script of onChange() when new row is inserted to "sheet2".
Your function of onChange() has already been installed as the OnChange installable trigger.
In this case, I thought that changeType and source of the event object can be used for achieving your goal. When this is reflected to your script, it becomes as follows.
Modified script:
function onChange(e) {
if (e.changeType != "INSERT_ROW" || e.source.getActiveSheet().getSheetName() != "sheet2") return; // Added
// do something
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet2"); // Or e.source.getSheetByName("sheet2");
var sheet2lr = ss.getRange(ss.getLastRow(), 1, 1, 2).getValues();
var sheet1lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1"); // Or e.source.getSheetByName("sheet1");
sheet1lr.getRange(getLastRow(), 1, 1, 4).setValues(sheet2lr)
}
In this modified script, when new row is inserted to "sheet2", the script below the if statement is run.
When you want to run this script, please insert new row to the sheet of "Sheet2".
Note:
If above modified didn't work, please reinstall the OnChange trigger to the function onChange.
For your script, I think that you can also the event object of e.source instead of SpreadsheetApp.getActiveSpreadsheet().
Reference:
Event Objects
Added:
About your following comment,
thanks Tanaike for help me. if the user adds a row to sheet2, the onchange function works(only change equlas oparator != change ==).actually another application is adding a row to sheet2. when if i use 1 control e.source.getActiveSheet().getSheetName() == "sheet2") working but if i use second control not fire onChange e.changeType != "INSERT_ROW".
In the current stage, in your situation, OnChange trigger can be used when the row is manually inserted and the row is inserted with Sheets API which is not Google Spreadsheet service (SpreadsheetApp). So from your comment, in your situation, I thought that the row is inserted with SpreadsheetApp. If my understanding is correct, I would like to propose the following 2 patterns.
Pattern 1:
In this pattern, your current script of onChange() is called from the function of actually another application is adding a row to sheet2. When your script of actually another application is adding a row to sheet2 is including in the same Google Apps Script project of onChange() and it supposes that the function name is sample(), you can modify as follows.
function sample() {
// do something of `actually another application is adding a row to sheet2`
onChange();
}
function onChange(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet2");
var sheet2lr = ss.getRange(ss.getLastRow(),1,1,2).getValues();
var sheet1lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1");
sheet1lr.getRange(getLastRow(),1,1,4).setValues(sheet2lr)
}
Pattern 2:
In this pattern, the row is inserted with Sheets API in your script of actually another application is adding a row to sheet2. In this case, you can convert the script for inserting row using InsertDimensionRequest of the batchUpdate method. Ref When this script is run, my proposed script can be used. Because when new row is inserted with Sheets API, the OnChange trigger is fired. In this case, I cannot understand about your script of actually another application is adding a row to sheet2. By this, I just propose the method for achieving your goal using Sheets API.
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 have 3 sheets my spreadsheet. Names is sheet1, sheet2, sheet3. And i have a function "onChange()". i install onChange function at triggers. i want run onChange function if only when add new row at sheet2 . But not working properly. if i write sheet1 or sheet2 or sheet3 anything always run my onChange function.
function onChange(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet2");
var sheet2lr = ss.getRange(ss.getLastRow(),1,1,2).getValues();
var sheet1lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1");
sheet1lr.getRange(getLastRow(),1,1,4).setValues(sheet2lr)}
I believe your goal as follows.
You want to run the script of onChange() when new row is inserted to "sheet2".
Your function of onChange() has already been installed as the OnChange installable trigger.
In this case, I thought that changeType and source of the event object can be used for achieving your goal. When this is reflected to your script, it becomes as follows.
Modified script:
function onChange(e) {
if (e.changeType != "INSERT_ROW" || e.source.getActiveSheet().getSheetName() != "sheet2") return; // Added
// do something
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet2"); // Or e.source.getSheetByName("sheet2");
var sheet2lr = ss.getRange(ss.getLastRow(), 1, 1, 2).getValues();
var sheet1lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1"); // Or e.source.getSheetByName("sheet1");
sheet1lr.getRange(getLastRow(), 1, 1, 4).setValues(sheet2lr)
}
In this modified script, when new row is inserted to "sheet2", the script below the if statement is run.
When you want to run this script, please insert new row to the sheet of "Sheet2".
Note:
If above modified didn't work, please reinstall the OnChange trigger to the function onChange.
For your script, I think that you can also the event object of e.source instead of SpreadsheetApp.getActiveSpreadsheet().
Reference:
Event Objects
Added:
About your following comment,
thanks Tanaike for help me. if the user adds a row to sheet2, the onchange function works(only change equlas oparator != change ==).actually another application is adding a row to sheet2. when if i use 1 control e.source.getActiveSheet().getSheetName() == "sheet2") working but if i use second control not fire onChange e.changeType != "INSERT_ROW".
In the current stage, in your situation, OnChange trigger can be used when the row is manually inserted and the row is inserted with Sheets API which is not Google Spreadsheet service (SpreadsheetApp). So from your comment, in your situation, I thought that the row is inserted with SpreadsheetApp. If my understanding is correct, I would like to propose the following 2 patterns.
Pattern 1:
In this pattern, your current script of onChange() is called from the function of actually another application is adding a row to sheet2. When your script of actually another application is adding a row to sheet2 is including in the same Google Apps Script project of onChange() and it supposes that the function name is sample(), you can modify as follows.
function sample() {
// do something of `actually another application is adding a row to sheet2`
onChange();
}
function onChange(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet2");
var sheet2lr = ss.getRange(ss.getLastRow(),1,1,2).getValues();
var sheet1lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1");
sheet1lr.getRange(getLastRow(),1,1,4).setValues(sheet2lr)
}
Pattern 2:
In this pattern, the row is inserted with Sheets API in your script of actually another application is adding a row to sheet2. In this case, you can convert the script for inserting row using InsertDimensionRequest of the batchUpdate method. Ref When this script is run, my proposed script can be used. Because when new row is inserted with Sheets API, the OnChange trigger is fired. In this case, I cannot understand about your script of actually another application is adding a row to sheet2. By this, I just propose the method for achieving your goal using Sheets API.
I have 2 Spreadsheets, namely Source and Destination. In Source, I have listed my sample products and their designated prices and are copied over to Destination using IMPORTRANGE function. Now, I'm trying to fire the OnChange trigger when there is an update on 'Destination Sheet 1'B:B only. Here's my code:
function testFunction(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Destination Sheet 1");
if (e.source.getSheetName = "Destination Sheet 1" && e.range.getRow() > 1 && e.range.getColumn() == 2){
var range = ss.getRange(e.range.getRow(), 3, ss.getLastRow());
range.setValue("There's an update");
}
}
I've also added the function testFunction on OnChange Trigger.
However, based on Event Objects Documentation there's no option for me to get the range that was updated from the events object. By "range", I mean the Destination Sheet 1'B:B. I'm getting this error:
"TypeError: Cannot read property 'getRow' of undefined at testFunction(Code:3:64)"
Desired Outcome (After an update on the pricing in 'Destination Sheet 1'!B:B):
I'm still fairly new to google scripts and I've really tried everything that I can think of but it is not getting me anywhere. Any help is greatly appreciated. Thank you in advance!
UPDATE 1:
It seems that there is no way of triggering the OnChange trigger when there is an update on a sheet that is on IMPORTRANGE function.
ALTERNATIVE:
Since it doesn't work, I was thinking of using OnEdit trigger instead on Source to handle that edit and apply my desired outcome on Destination. However, I'm having permission issues.
Exception: You do not have permission to call SpreadsheetApp.openById. Required permissions: https://www.googleapis.com/auth/spreadsheets
Here's my code:
function onEdit(e){
aSourceFunction(e);
}
function aSourceFunction(e) {
//IF stmt to make sure that OnEdit Trigger is not triggerred anywhere
if (e.source.getSheetName = "Source Sheet 1" && e.range.getRow() > 1 && e.range.getColumn() == 2){
//my attempt to call Destination Sheet 1 from Destination
var ss = SpreadsheetApp
.openByID("1QMtU6VbNQyDLXMHmaLBBDePH5l")
.setActiveSheet("Destination Sheet 1");
//desired outcome
ss.getRange(e.range.getRow(), 4, ss.getLastRow()).setValue("There's an update");
}
}
Also, I'm not sure if my concern is related but I've read somewhere that my code shouldn't have #OnlyCurrentDoc but I don't have that anywhere in my codes even in my Manifest file.
{
"timeZone": "Asia/Hong_Kong",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
Here's the scope of it:
Update 2: It worked! Initially, I have a simple OnEdit trigger. However, according to this, you need to have an installable OnEdit trigger for it to work (also fixed my code for the errors) and when I ran the code, the "permission prompt" showed up for me to access the Destination Spreadsheet. Here's my code for anyone who needs it:
function aSourceFunction(e) {
//IF stmt to make sure that OnEdit Trigger is not triggerred anywhere
if (e.source.getSheetName = "Source Sheet 1" && e.range.getRow() > 1 && e.range.getColumn() == 2){
//my attempt to call Destination Sheet 1 from Destination
var ss = SpreadsheetApp.openById("1QMtU6VbNQyDLXMHmaLBBDePH5l-4wDPUNQ827gb3jXY");
var sheet = ss.getSheetByName("Destination Sheet 1");
//desired outcome
sheet.getRange(e.range.getRow(), 4).setValue("There's an update");
}
}
Note: Go to Edit>Current project's trigger>Add Trigger using aSourceFunction as your function and OnEdit as the trigger.
From the question
However, based on Event Objects Documentation there's no option for me to get the range that was updated from the events object. By "range", I mean the Destination Sheet 1'B:B.
You are right, there is no way to get the range that was udpdated from the change event object.
But if you already know that range of interest is 'Destination Sheet 1'!B:B you can use:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var range = spreadsheet.getRange("Destination Sheet 1!B:B");
Regarding the error message it occurs because the change event object doesn't include a range property.
Please bear in mind that (from Installable Triggers)
An installable change trigger runs when a user modifies the structure of a spreadsheet itself—for example, by adding a new sheet or removing a column.
Regarding how to make the OnChange trigger to fire on updates on Column B only, you can try to use getActiveRange() but sometimes it doesn't work. There is an open issue related to this getActiveRange() incorrect in onChange trigger for some column/row operations
Related
How to identify changed cell in onChange function
Why Class Range getValues sometimes returns [[]] when chained to Class Sheet getActiveRange?
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.