This question already has an answer here:
Merging or Combining two onEdit trigger functions
(1 answer)
Closed 1 year ago.
I got two onEdit() function scrips on a Google spreadsheet. But seems like one function is working at a time.
First function is a script to color all Rows, and second one is date function for adding dates on 2 cells based on column edit.
Here are the scripts.
function colorAll()
{
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 4;
var endRow = sheet.getLastRow();
for (var r = startRow; r <= endRow; r++) {
colorRow(r);
}
}
SpreadsheetApp.flush();
function colorRow(r)
{
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getRange(r, 1, 1, 32);
var data = dataRange.getValues();
var row = data[0];
SpreadsheetApp.flush();
if(row[14] === ""){
dataRange.setBackgroundRGB(255, 255, 255);
dataRange.setFontColor("#000000");
}
else if(row[14] === "BEING USED") {
dataRange.setBackgroundRGB(150, 185, 255);
dataRange.setFontColor("#004BE1");
}
}
function onEdit(event)
{
var r = event.source.getActiveRange().getRowIndex();
if (r >= 2) {
colorRow(r);
}
}
function onOpen(){
colorAll();
And the second function.
function onEdit(e) {
var aCell = e.source.getActiveCell(), col = aCell.getColumn();
if(col == 19 || col == 21) {
var adjacentCell = aCell.offset(0, 1);
var newDate = Utilities.formatDate(new Date(),
"GMT+1", "dd/MM/yyyy");
adjacentCell.setValue(newDate);
}
}
The date function is working but the colorRow function is not, if I remove the date script then the colorRow will work.
Can any one point me in the right direction? Seems like I am missing something
Thanks
Barry Smith's comment about "two functions with same name" is right; only the second would execute in that case.
You can have just one spreadsheet-contained function named onEdit(). If you want to use another function as an onEdit trigger, you need to set it up as an installable trigger.
You can use the dialog from Resources -> Current Project's Triggers to install the second trigger.
Alternatively, you could have just one onEdit() simple trigger, but have it call the "sub-trigger" functions, passing the event object e to each of them.
Background: Guide to Triggers.
I might be misunderstanding, but it looks like you have two different functions with the exact same name. This can't happen, because the second one is basically overwriting the first one. Give the different names and it should work.
Found a very simple solution for this, just add the below to the start of your script:
function onEdit(e){
onEdit1(e);
onEdit2(e)
}
I rename my onEdit accordingly e.g. instead of onEdit1(e) I'll use hideRow(e) or whatever is relevant.
Here's a full code using three onEdit functions (automatically adding time to an edited cell, auto hiding a row when using a tick box and auto inserting a row):
function onEdit(e) {
autoTime(e);
hideRow(e)
}
function autoTime(e) {
var s = SpreadsheetApp.getActiveSheet();
if (s.getName() == "Sheet1") {
var r = s.getActiveCell();
if (r.getColumn() == 2) {
var nextCell = r.offset(0, 1);
if (nextCell.getValue() === '')
nextCell.setValue(new Date());
}
}
}
function hideRow(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
if (e.range.columnStart != 17 || e.value != "TRUE") return;
SpreadsheetApp.getActiveSheet().hideRows(e.range.rowStart);
}
function insertRow(e) {
var sheet = e.range.getSheet();
var lastRow = sheet.getLastRow();
if (e.range.getRow() > lastRow - 5)
sheet.insertRowAfter(lastRow - 1);
}
Related
I have created 2 functions that run onEdit (tested and working when named onEdit)
The first one sets the value (new Date) of a row >7 in Column D onEdit of Column C.
function CompleteTime() { //Function to add a time stamp to Complete Time Column D
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = sheet.getActiveCell();
var row = range.getRow();
var col = range.getColumn()
if(row>7 && col==3){
sheet.getRange(row, 4).setValue(new Date());
}
}
The second one sets a formula in Row 8 Column E onEdit of Row 8 Column D
function Duration() { //Function to set formula in column E to calculate duration from start time to first complte time
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = sheet.getActiveCell();
var row = range.getRow();
var col = range.getColumn()
if(row==8 && col==4){
sheet.getRange(row, 5).setFormula("=SUM(D8-B3)");
}
}
I have created 2 installable triggers to run these functions onEdit, and the first one runs fine, but the second one doesn't trigger the first one. The value that is entered by the first function doesn't trigger the second function.
Everything I've read suggests this is the way to get multiple onEdits to run in a single sheet, but I am stuck here.
One of the restrictions of triggers is that "Script executions and API requests do not cause triggers to run", meaning that you need to manually include the Duration() call after inserting the completion time.
The example below isn't the only way to accomplish this, but it should give you an idea of what I'm trying to describe.
function onEdit(e) {
var row = e.range.rowStart;
var col = e.range.columnStart;
if (row == 8 && col == 4) {
insertDurationFormula(e.range.offset(0, 1));
} else if (row > 7 && col == 3) {
insertCurrentTime(e.range.offset(0, 1));
insertDurationFormula(e.range.offset(0, 2));
}
}
function insertCurrentTime(cell) {
cell.setValue(new Date());
}
function insertDurationFormula(cell) {
cell.setFormula("=SUM(D8-B3)");
}
Also note that I'm using the event object included with edit triggers. Using the event object can help simplify your code a bit and reduce unnecessary calls.
It is not good practice to have multiple onEdit triggers in one spreadsheet
In case of simple onEdit triggers, it is not possible to have more than one per Apps Script project, in case of installable ones - it can cause to conflicts.
Instead, have only one function bound on a trigger and call from there on other function depending on the condition.
Sample:
function bindmeOnTrigger() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = sheet.getActiveCell();
var row = range.getRow();
var col = range.getColumn()
if(row==8 && col==4){
function1();
} else if(row>7 && col==3){
function2();
}
}
function function1(){
...
}
function function2(){
...
}
Or simply:
function bindmeOnTrigger() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = sheet.getActiveCell();
var row = range.getRow();
var col = range.getColumn()
if(row==8 && col==4){
sheet.getRange(row, 5).setFormula("=SUM(D8-B3)");
} else if(row>7 && col==3){
sheet.getRange(row, 4).setValue(new Date());
}
}
I am new to Google apps script (and javascript for that matter).
In the code below I keep getting "No logs found. Use Logger API to add logs to your project.", tried adding an "event" variable to the onEdit() trigger with no avail.
function onEdit() {
var test = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("test");
var activeCell = test.getActiveCell();
var col = activeCell.getColumn();
var row = activeCell.getRow();
Logger.Log(col);
Logger.Log(row);
if(col == 1 && row == 1) {
var testVar = 1;
Logger.log(testVar);
}
}
Thanks.
You can play around with this to learn more about the event object. It really has a lot of information in it right of out the box.
function onEdit(e) {
if(e.range.getSheet().getName()!='test'){return;}//keeps other pages from affecting the performance of the onEdit function
var activeCell = e.range;//typically e.range is one cell
var col = e.range.columnStart;//there's also a columnEnd
var row = e.range.rowStart;//there's also a rowEnd
Logger.log(JSON.stringify(e));//take a look at this and see what else is there
Logger.Log(col);
Logger.Log(row);
if(col == 1 && row == 1 && e.range.getSheet().getName()=='test') {
var testVar = 1;
Logger.log(testVar);
}
}
Can anyone help me do this?
My problem is that all of my three (3) codes has the same name "onEdit".
How can I run three (3) of of them?
There is a suggestion that I need to change their name, but when I change the names my codes wont run.
Can anyone post an example code for me, for my reference?
Please?
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
for (p=1 ; p<=lastRow ; p++) { // p <= lastRow
var status = sheet.getRange("C"+p).getValue(); // Change P to the completed column
if (status == "no") { // status == "no"
sheet.hideRows(p);
}
}
}
function onEdit() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var cell = spreadsheet.getActiveCell();
var col = cell.getColumn();
var row = cell.getRow();
var rows = [1, 2, 3];
// This is a list of the rows that should blink if edited.
if (col === 7 && rows.indexOf(row) !== -7 && sheet.getName() === 'Sheet1') {
// If the edited cell is in column A (1) and if the edited cell
// is one of the rows listed
for (var num = 0; num < 50; num++) {
var colour = num%2 === 0
? 'GOLD'
: 'WHITE';
// Using ? and : like this is called a ternary operation. It's a
// shorter form of if. ifStatement ? true : false.
sheet.getRange('G' + row + ':G' + row).setBackground(colour);
// Get the range for the edited row and set the bg colour
SpreadsheetApp.flush();
Utilities.sleep(500);
}
}
}
Two functions in one onEdit()
Here's an example that combines two functions into one onEdit() simple trigger.
function onEdit(e){
var ss=e.source;
var rg=e.range;
var sh=rg.getSheet();
var name=sh.getName();
Logger.log('Name: %s',name);
var includedSheets=['Sheet45','Sheet46']
if(includedSheets.indexOf(name)==-1){
return;
}
if(name=='Sheet45'){
Sheet45(e);//You could put the code right here but I wanted to make it clear that it's two different operation. This one deletes a row if all conditions are met.
}
if(name=='Sheet46'){
Sheet46(e);//This one deletes a column if all conditions are met
}
}
function Sheet45(e){//You can name them whatever you want
var sh=e.range.getSheet();
var row=e.range.getRow();
var col=e.range.getColumn();
Logger.log('Name: %s',e.range.getSheet().getName());
if(col==1 && row==4 && e.value=='delete'){//if column1 and row4 is changed to 'delete'
sh.deleteRow(row);//the it deletes row 4
}
}
function Sheet46(e){
var sh=e.range.getSheet();
var row=e.range.getRow();
var col=e.range.getColumn();
Logger.log('Name: %s',e.range.getSheet().getName());
if(row==1 && col==4 && e.value=='delete'){//if column4 and row1 is changed to 'delete'
var rg=sh.getDataRange();//then it deletes column 4
var vA=rg.getValues();
for(var i=0;i<vA.length;i++){
vA[i].splice(3,1);
Logger.log('vA[%s]: %s',i,vA[i]);
}
}
rg.clear();
sh.getRange(1,1,vA.length,vA[0].length).setValues(vA);
}
There are a lot of different ways to do it. I like to return as quickly as possible on sheets that are not involved in any of the functions.
Simple Triggers
Spreadsheet Documentation
You probably already know that you can't run these onEdit() functions directly unless you provide the event object.
Your two functions combined
I modified the second one a bit because it didn't make sense to me. So you change it since you probably understand what you want.
function onEdit(e){
oE1(e);
oE2(e);
}
function oE1(e) {//this works on all sheet
var ss=e.source;
var rg=e.range;
var sh=rg.getSheet();
var name=sh.getName();
var lastRow = sh.getLastRow();
for(var row=1;row<=lastRow;row++) {
var status=sh.getRange(row,3).getValue();//Column3 is C
if (typeof(status)=="string" && status.toLowerCase()=="no") {
sh.hideRows(row);
}
}
}
function oE2(e) {//this only works on sheet 1
var ss=e.source;
var rg=e.range
var sh=rg.getSheet();
var row=rg.getRow();
var col=rg.getColumn();
var cell=sh.getRange(row,col);
var rows = [1, 2, 3];
if(col==1 && rows.indexOf(row)!=-1 && sh.getName()=='Sheet1') {
sh.getRange(row,7).setBackground((rows.indexOf(row)%2==0)?'Gold':'White');
}
}
It would be nice to rework the organization so that you can return more quickly for sheets that are not involved with either function. It's really helpful to look at the executions page when debugging these functions.
I am using 2 functions in a tracker that I have. 1 function is for sorting column C by date (earliest first) automatically when a cell in that column is edited. If the word 'done' or 'void' is typed into that cell then the entire row is copied onto the next sheet.
However my issue is that when I type 'done' or 'void' into this cell, Google sheets is sorting the list and also processing the move function and thus moving the wrong row (the resulting row number, after the sort). How can i fix or improve the code.
function movedonevoid(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
var value = r.getValue();
if(s.getName() == "tracker" && r.getColumn() == 3 && (value=="done" || value=="void" )) {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("tracker2");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
and
function sorttracker(event){
var sheet = event.source.getActiveSheet();
if(sheet.getName() == 'tracker'){
var editedCell = sheet.getActiveCell();
var columnToSortBy = 3;
var tableRange = "A2:D"; // What to sort.
if(editedCell.getColumn() == columnToSortBy) {
var range = sheet.getRange(tableRange);
range.sort( { column : columnToSortBy, ascending: true } );
}
}
}
I've inserted the above 2 bits of code as 2 different scripts and then used project triggers for both 'on edit'. Not sure if that is the best approach?
As you already noticed, having two different functions acting over the same range and being called by two different onEdit triggers isn't a good idea.
Instead of using two onEdit triggers, use only one to call an orchestral conductor function that will call the original two. The orchestral conductor function could be something like the following:
function onEdit(e) {
movedonevoid(e);
sorttracker(e);
}
I have a Spreadsheet with some functions. One of them is a onEdit(event) function that copies some values to other sheets based on conditions. This is the code (simplified but with the important parts intact):
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.range;
if(s.getName() === "Lista" && r.getColumn() === 9 && r.getValue() === "Posicionada") {
var sheetname = s.getRange(r.getRow(),3).getValue();
var columnRef = s.getRange(r.getRow(),4).getValue();
var row = s.getRange(r.getRow(),5).getValue();
var targetSheet = ss.getSheetByName("Mapa " + sheetname);
var headers = targetSheet.getRange(1, 1, 1, targetSheet.getLastColumn());
for (var i = 0; i < headers; i++) {
if (headers[i] === columnRef) {
break;
}
}
var column;
if (columnRef === "A1") {
column = 2;
}
else if (columnRef === "A2") {
column = 3;
}
else if (columnRef === "B1") {
column = 4;
}
else if (columnRef === "B2") {
column = 5;
}
if (sheetname === "N2") {
row = row - 30;
}
if (sheetname === "N3") {
column = column - 10;
row = row - 42;
}
targetSheet.getRange(row,column).setValue(s.getRange(r.getRow(), 1, 1, 1).getValue());
}
}
The code works as it should when I manually edit the cell. But, I have a code that edit the cell when the user press a button in a sidebar, this is the code:
function positionMU(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveCell().activate();
var cellLevel = cell.offset(0,2);
var cellLetter = cell.offset(0,3);
var cellNumber = cell.offset(0,4);
var cellStatus = cell.offset(0,8);
var dbq = "Posicionada";
var fora = "Pendente de recebimento";
if (cellStatus.getValue() == "Aguardando posicionamento"){
cellStatus.setValue(dbq); //attention in this line
}
else if (cellStatus.getValue() == "Aguardando saĆda"){
cellStatus.setValue(fora);
var cellExitDate = cell.offset(0,6);
cellExitDate.setValue(getDate());
}
}
As you can see, this function change the cell content with setValue(), but, when I use this function, the value of the cell changes, but the onEdit() trigger doesn't work.
How can I make the onEdit() trigger recognize changes made with setValue()?
You are right. onEdit() only triggers if the range is edited manually. As can be seen here, onEdit() triggers when a value is changed by the user.
I tested the function by making function to insert values into a column for which my onEdit responds and nothing happens. Including various other techniques that I could think of. Best thing to do here is to suggest this as an enhancement on App Script's Issue Tracker.
However, I made it work by writing another function to be called when another function in the script makes changes to the sheet. These are the test functions I wrote:
function addValues()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var book = "Book";
var cancel = "Cancel";
var maxRow = range.getLastRow()+1;
for(var i=0; i<4; i++)
{
if (i%2 == 0)
{
sheet.getRange(maxRow, 1).setValue(book);
autoChanges(maxRow);
}else{
sheet.getRange(maxRow, 1).setValue(cancel);
autoChanges(maxRow);
}
maxRow++;
}
}
autoChanges function:
function autoChanges(row)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var data = range.getValues();
var response = "";
sheet.getRange(row, 2).protect();
response = data[row-1][0];
if (response == "Book")
{
sheet.getRange(row, 2).canEdit();
}else{
sheet.getRange(row, 2).setValue("--NA--");
}
}
Not the most elegant solution but this seems to be the only workaround for what you are trying to do.
There are some very good reasons why calling range.setValue() doesn't trigger the onEdit event, and most of them have to do with infinite recursion. In fact, you call setValue() yourself WITHIN onEdit(). This would trigger a recursive call, and from what I can see, you have no provision for handling the base case and thus your code would explode if setValue() did what you want.
Why not simply take all of your code out of your event handler, and put it into another function:
function onEdit (e) {
return handleEdits(e.range);
}
function handleEdits(r) {
s = r.getSheet();
ss = s.getParent();
//the rest of your code should drop right in.
}
then, inside your autoChanges function, go ahead and call handleEdits, passing it an appropriate range, after your call to setValue().
If you like to play with fire, and I personally do, you can call the onEdit(e) function after you make a change. Just send in an object whatever is called and used by the e object.
For me, I just needed to add:
var e={};
e.range=the range you are making a change to in the script;
e.source = SpreadsheetApp.getActiveSpreadsheet();//or make sure you have the sheet for wherever you are setting the value
e.value=whatever value you are setting
e.oldValue=if you onEdit needs this, set it here
//if you are using any of the other standard or special properties called by your onEdit...just add them before calling the function.
onEdit(e);//call the function and give it what it needs.