How to make onEdit() trigger function apply to multiple sheets - javascript

I have a Google Sheet I'm working on.
I have a script that fills in the column B with a timestamp when I update column A on the first tab. However I need it to do the same on the second tab, but I can't get it to work there. What do I need to change?
The current script I'm using is:
function onEdit(e) {
var sheetToWatch= 'Wrong Grading',
columnToWatch = 1,
columnToStamp = 2; //change all of these to your needs
if (e.range.columnStart !== columnToWatch
|| e.source.getActiveSheet().getName() !== sheetToWatch
|| !e.value)
return;
e.source.getActiveSheet()
.getRange(e.range.rowStart, columnToStamp)
.setValue(new Date());
}

For future readers, the code snippet in Paul's question is derived from code on the Google Docs help forum, which includes a detailed line-by-line explanation.
The function uses the variable sheetToWatch to identify one sheet (aka "tab") that the onEdit() function cares about. That is validated by this comparison:
|| e.source.getActiveSheet().getName() !== sheetToWatch
...and if the source of the current trigger event is not matched, the function exits without doing anything.
What do I need to change? If you want this function to work on all sheets in the Spreadsheet, then you can just eliminate this check altogether:
function onEdit(e) {
var columnToWatch = 1,
columnToStamp = 2; //change all of these to your needs
if (e.range.columnStart !== columnToWatch
|| !e.value)
return;
e.source.getActiveSheet()
.getRange(e.range.rowStart, columnToStamp)
.setValue(new Date());
}
If you want to have the onEdit() operate on a set of sheets, then you can change that above comparison to check if the current sheet's name is found in an array of sheet names. That will change the comparison to this:
|| sheetsToWatch.indexOf( e.source.getActiveSheet().getName() ) === -1
You can learn more about the indexOf() method here. What it's doing though, is getting the name of the event trigger source sheet, finding it in the sheetsToWatch array, and returning the found index. If the sheet does not appear in the array, indexOf() returns -1.
The resulting function is:
function onEdit(e) {
var sheetsToWatch= ['Wrong Grading',
'Something Else'],
columnToWatch = 1,
columnToStamp = 2; //change all of these to your needs
if (e.range.columnStart !== columnToWatch
|| sheetsToWatch.indexOf( e.source.getActiveSheet().getName() ) === -1
|| !e.value)
return;
e.source.getActiveSheet()
.getRange(e.range.rowStart, columnToStamp)
.setValue(new Date());
}

Related

How to Auto-Update or Auto-Delete Google Calendar Events when Cells are Updated in the Google Sheet the Event is Created From From?

I have reviewed other answers to seemingly similar questions and haven't been able to find a solution yet. I am currently using the below script (thanks to a lot of help!) to auto-populate events from multiple sheets in a workbook; however, what I am also hoping to achieve:
the events auto update in the calendar if pre-existing data in C4:F24 is changed (without a duplicate event being created)
the event deletes if data in cells E4:F24 is deleted.
I already have an "onEdit" trigger enabled; however, it does not allow for any of the above. Any help is incredibly appreciated! I have updated the script to reflect what I am currently testing. This script returns the following error when the installed "OnEdit" trigger runs: ReferenceError: e is not defined
at simpleSheetsToCalendar(Code:4:17)
function simpleSheetsToCalendar() {
var sheetNames = ["Oct-08-2021 | Plan", "Oct-15-2021 | Plan"]; // Please set the sheet names you want to use.
var calendarId = "myCalendar"; // Please set your calendar ID.
var {range} = e;
var sheet = range.getSheet();
if (!(sheetNames.includes(sheet.getSheetName()) && range.rowStart >= 4 && range.rowEnd <= 24 && range.columnStart >= 3 && range.columnStart <= 6)) return;
// 1. Retrieve all events from the Calendar and create an object for checking the duplicated titles.
var events = [];
var pageToken = "";
do {
var res = Calendar.Events.list(calendarId, {maxResults: 2500, fields: "nextPageToken,items(id,summary,start(dateTime),end(dateTime))", pageToken});
events = events.concat(res.items);
pageToken = res.nextPageToken;
} while(pageToken);
var obj = events.reduce((o, e) => Object.assign(o, {[e.summary]: e}), {});
// 2. Retrieve sheets from a Google Spreadsheet and retrieve the values from "C4:F24", and create an object for creating new events, deleting events and updating events.
var values = sheet.getRange("C4:F24").getValues().reduce((o, [title,,startTime,endTime]) => {
if (!obj[title] && title && startTime && endTime && !o[title]) {
o.add[title] = {startTime, endTime};
} else if (obj[title] && title && startTime && endTime) {
if (new Date(obj[title].start.dateTime).getTime() != startTime.getTime() || new Date(obj[title].end.dateTime).getTime() != endTime.getTime()) {
obj[title].start.dateTime = startTime;
obj[title].end.dateTime = endTime;
o.modify.push(obj[title]);
}
} else if (obj[title] && title && !startTime && !endTime) {
o.remove.push(obj[title].id);
}
return o;
}, {add: {}, remove: [], modify: []});
// 3. Create new events.
var eventCal = CalendarApp.getCalendarById(calendarId);
var ar = Object.entries(values.add);
if (ar.length > 0) {
ar.forEach(([title, {startTime, endTime}]) => eventCal.createEvent(title, startTime, endTime));
}
// 4. Modify events.
if (values.modify.length > 0) {
values.modify.forEach(({id, start, end}) => eventCal.getEventById(id).setTime(start.dateTime, end.dateTime));
}
// 5. Delete events.
if (values.remove.length > 0) {
values.remove.forEach(id => eventCal.getEventById(id).deleteEvent());
}
}
From your following question and your replying,
the events auto update in the calendar if pre-existing data in C4:F24 is changed (without a duplicate event being created)
the event deletes if data in cells E4:F24 is deleted.
In your situation, I understood as follows.
You want to automatically execute the script when the cells "C4:C24" of the specification sheets are edited.
When the columns "E" and "F" are changed while the column "C" has the value, you want to change the start and endtime of the event.
When the cells "E4:F24" are deleted while the cells "C4:C24" are not deleted, you want to delete the events from the calendar.
In this case, how about the following flow?
Retrieve all events from the Calendar and create an object for checking the duplicated titles.
Retrieve sheets from a Google Spreadsheet and retrieve the values from "C4:F24", and create an object for creating new events, deleting events and updating events.
When new title is found, create new events.
When the title is existing and the start and end times are changed, modify events.
When the title is existing and the start and end times are deleted, Delete events.
And, in order to execute the script, in this case, the installable OnEdit trigger is used. Because Calendar API is used. When this flow is reflected in your script, it becomes as follows.
Modified script:
In this sample script, in order to retrieve all titles of the calendar event, Calendar API is used. So before you use this script, please enable Calendar API at Advanced Google services. And, please set sheetNames and calendarId.
And also, please install the installable OnEdit trigger to the function simpleSheetsToCalendar.
function simpleSheetsToCalendar(e) {
var sheetNames = ["Sheet1", "Sheet2",,,]; // Please set the sheet names you want to use.
var calendarId = "calendarId"; // Please set your calendar ID.
var {range} = e;
var sheet = range.getSheet();
if (!(sheetNames.includes(sheet.getSheetName()) && range.rowStart >= 4 && range.rowEnd <= 24 && range.columnStart >= 3 && range.columnStart <= 6)) return;
// 1. Retrieve all events from the Calendar and create an object for checking the duplicated titles.
var events = [];
var pageToken = "";
do {
var res = Calendar.Events.list(calendarId, {maxResults: 2500, fields: "nextPageToken,items(id,summary,start(dateTime),end(dateTime))", pageToken});
events = events.concat(res.items);
pageToken = res.nextPageToken;
} while(pageToken);
var obj = events.reduce((o, e) => Object.assign(o, {[e.summary]: e}), {});
// 2. Retrieve sheets from a Google Spreadsheet and retrieve the values from "C4:F24", and create an object for creating new events, deleting events and updating events.
var values = sheet.getRange("C4:F24").getValues().reduce((o, [title,,startTime,endTime]) => {
if (!obj[title] && title && startTime && endTime && !o[title]) {
o.add[title] = {startTime, endTime};
} else if (obj[title] && title && startTime && endTime) {
if (new Date(obj[title].start.dateTime).getTime() != startTime.getTime() || new Date(obj[title].end.dateTime).getTime() != endTime.getTime()) {
obj[title].start.dateTime = startTime;
obj[title].end.dateTime = endTime;
o.modify.push(obj[title]);
}
} else if (obj[title] && title && !startTime && !endTime) {
o.remove.push(obj[title].id);
}
return o;
}, {add: {}, remove: [], modify: []});
// 3. Create new events.
var eventCal = CalendarApp.getCalendarById(calendarId);
var ar = Object.entries(values.add);
if (ar.length > 0) {
ar.forEach(([title, {startTime, endTime}]) => eventCal.createEvent(title, startTime, endTime));
}
// 4. Modify events.
if (values.modify.length > 0) {
values.modify.forEach(({id, start, end}) => eventCal.getEventById(id).setTime(start.dateTime, end.dateTime));
}
// 5. Delete events.
if (values.remove.length > 0) {
values.remove.forEach(id => eventCal.getEventById(id).deleteEvent());
}
}
When this script is run, the events for updating and deleting are retrieved from all event lists from Calendar and the values from Spreadsheet. And, when the values of columns "E" and "F" are changed, the event with the title of column "C" is updated. When the values of columns "E" and "F" are empty, the event with the title of column "C" is deleted.
Note:
In this answer, from your question, it supposes that when the values of columns "E" and "F" are removed, the values of the title of column "C" are existing. Please be careful about this.
In this modified script, the script is automatically executed when the cells "C4:F24" of the specification sheets. So when you directly run the function, an error like Cannot destructure property 'range' of 'e' as it is undefined. occurs. When you run the script, please edit the cells "C4:F24" of the specific sheets of sheetNames. Please be careful this.
Please confirm your sheet names. Please be careful this.
References:
setTime(startTime, endTime)
deleteEvent()
Installable Triggers

Google Apps Script multiple IF statements in an onEdit function

Background
I'm trying to write a script that will check two different cells to see if the checkbox has been checked. So, it would first check one cell (AL4) to see if the value is true, and then a different cell (Q4) to see if the value is true. If the value is true, then it will run a different function.
Both of the cells I am checking are in the same sheet, "Dashboard", so it doesn't have to find different sheet names. I've been different ways to get it to check the different cells by using .getcolumn and .getrow commands but I've had some trouble with that too. I've also run into some problems where it will ignore the if statement and run the function on every edit and not just when the two cells are checked.
How can I get an if and else if statement to work in the same onEdit function?
Current Script
function onEdit(e) {
const sheetName = "Dashboard"
var range = e.range;
if (range.getSheet().getSheetName() != sheetName || range.getA1Notation() != "AL4" || !range.isChecked()) {
return;
RefreshCheckmark2();
} else if (range.getSheet().getSheetName() != sheetName || range.getA1Notation() != "Q4" || !range.isChecked()) {
return;
InverseYieldRefresh();
}
}
Also, the above code is completely functional if you remove the else if statement and the brackets after the first If statement.
Ideally I would like to be able to add more else if statements so that it can check more cells because I like to have checkboxes run commands and if my understanding is correct, you can't have more than one onEdit function in a single spreadsheet.
Thank you for any insight!
I think what you are trying to do right now is to check if it is not AL4 or Q4 rather than checking if it is AL4 or Q4. Also, in your current code, you use return; before running any function so it would really skip running the function even if it goes in your if-statement. You can try this code below:
function onEdit(e) {
const sheetName = "Dashboard"
var range = e.range;
if (range.getSheet().getSheetName() == sheetName && range.getA1Notation() == "AL4" && range.isChecked()) {
RefreshCheckmark2();
}
else if (range.getSheet().getSheetName() == sheetName && range.getA1Notation() == "Q4" && range.isChecked()) {
InverseYieldRefresh();
}
}
I only changed the conditions to make it check if it is AL4 or Q4 then removed the return; to make the functions inside the if-statements run once conditions are satisfied.
Let me know if this solves your problem.

Merging or Combining two onEdit trigger functions

I have script for Google Sheets that I collected on interwebs and got some help here. No I have 2 onEdit in conflict. I overcome that by creating script Trigger for onEdit2. It works but I don't think it is the best solution. Could you help get those two separated onEdit with if functions into one, please?
//Dependent Dropdown list
function onEdit(e){ // Function that runs when we edit a value in the table.
masterSelector(master1,master2,master3,master4);
var activeCell = e.range; // It returns the coordinate of the cell that we just edited.
var val = activeCell.getValue(); // Returns the value entered in the column we just edited.
var r = activeCell.getRow(); // returns the row number of the cell we edit.
var c = activeCell.getColumn(); // returns the column number of the cell we edit.
var wsName = activeCell.getSheet().getName();
if (wsName === masterWsName && c === firstLevelColumn && r > masterNumberOfHeaderRows) { // the if delimits the section sensitive to modification and action of the onEdit.
applyFirstLevelValidation(val,r);
} else if (wsName === masterWsName && c === secondLevelColumn && r > masterNumberOfHeaderRows){
applySecondLevelValidation(val,r);
}
} // end of onEdit
// addRow by checkboxes
function onEdit2(e) {
masterSelector(master1,master2,master3,master4);
//IF the cell that was edited was in column 4 = D and therefore a checkbox AND if the cell edited was checked (not unchecked):
if (e.range.columnStart === 4 && e.range.getValue() === true) {
var sheet = SpreadsheetApp.getActiveSheet(),
row = sheet.getActiveCell()
.getRow(),
//(active row, from column, numRows, numColumns)
rangeToCopy = sheet.getRange(row, 1, 1, 30);
sheet.insertRowAfter(row);
rangeToCopy.copyTo(sheet.getRange(row + 1, 1));
//Reset checked boxes in column 4
sheet.getRange(row,4,2,1).setValue(false);
}
}
Whole script is here, if needed.
A script cannot contain two functions with the same name. Rename your first onEdit function to onEdit1 (actually it will be better to assign a descriptive name) and the second function as onEdit2, then put them both in one function named onEdit and pass the parameter e to both of them:
function onEdit(e){
onEdit1(e);
onEdit2(e);
}
Related:
Two OnEdit functions not working together
Best Practices for Multiple OnEdit Functions
How to run multiple onEdit functions in the same google script (google sheets)?
Bracketing multiple onEdit functions

GAS OR operator makes both tests ignored

I have this function looping through all sheets, and if the sheet is not hidden, add the sheet name to the array out.
function sheetnames() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var out = new Array()
for (var i=0 ; i<sheets.length ; i++)
if (sheets[i].isSheetHidden()!= true){
out.push( [ sheets[i].getName() ]
)}
Logger.log(out);
}
I would also like to test for specific sheet names, which I am able to do with
if (sheets[i].getSheetName()!= 'Sheet1'){
However when I put them together with || an OR operator, both tests are ignored.
if (sheets[i].isSheetHidden()!= true || sheets[i].getSheetName()!= 'Sheet1'){
I'm not sure if it is the way I am handling || or something else I'm not seeing.
In this example, sheet1 is visible, so would pass the first part of the test.
(sheets[i].isSheetHidden()!= true || sheets[i].getSheetName()!= 'Sheet1')
will return true if the current sheet is not hidden or not named 'Sheet1'. Or in other words, it will only return true if the current sheet is named 'Sheet1' and is hidden. That's probably not what you want, is it? Perhaps what you're looking for is the && AND logical operator?
Also, I'd suggest you look into formatting code, why you should use !== instead of !=, and see this question for information on using var a = []; compared to var b = new Array();

google apps script not working

I have this code that works for restricting data in all cells to 5 characters. I like that it shortens the data back to 5 characters if you put more that 5 in.
function onEdit(e) {
var limit = 5;
if(e.value.length > limit) {
e.range.setValue(e.value.substring(0, limit));
}
}
If I click the Run button it does show an error.
TypeError: Cannot read property "value" from undefined. (line 3, file "Code")
But it seems to work fine.
I would like to have the script work on a certain range of cells only. Say C3:C100. So I had to reference the sheet and cells, so changed the script to this.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("C3:C100");
var limit = 5;
if(e.value.length > limit) {
e.range.setValue(e.value.substring(0, limit));
}
}
This script does not work on any of the cells, let alone the selected ones.
Can anyone help me please? What have I missed?
A onEdit script is triggered by any edit in a spreadsheet. It 'captures' the edit in what is called an event object. The event object can be passed as an argument to the function. In that case you'll see onEdit(e) or onEdit(event) as the function name. That object has some parameters attached to it: e.range (the edited range), e.value (the value of the edit), e.source (the spreadsheet where the edit is done), etc..
Now when you run the function from the script editor (by clicking the play button) no actual edit is taking place in the spreadsheet. Therefore no event object will be created and all parameters of that object will be undefined.
If you want to 'limit' the scope of the script to the range C3:C100, you can change the script as follows:
function onEdit(e) {
var limit = 5;
if (e.value.length > limit && e.range.rowStart > 2 && e.range.rowStart < 101 && e.range.columnStart == 3) {
e.range.setValue(e.value.substring(0, limit));
}
}
Note that currently the script will work on every tab/sheet inside your spreadsheet/workbook. If you want to further limit the script to work on only one sheet (e.g. 'Sheet1'), you can try something like
function onEdit(e) {
var limit = 5;
if (e.source.getActiveSheet().getName() == 'Sheet1' && e.value.length > limit && e.range.rowStart > 2 && e.range.rowStart < 101 && e.range.columnStart == 3) {
e.range.setValue(e.value.substring(0, limit));
}
}

Categories