Making onEdit() faster and more efficient - google-apps-script

After several googling, I arrived with a code that looks into a main sheet and shows or hides sheets (named A to N) depending on what checkbox is ticked/unticked (main requirement).
However, it seems to be not that efficient, and takes a couple of seconds to truly show/hide the sheets after ticking/unticking their respective checkboxes. I've recently learned that onEdit() always runs whenever there's an Edit anywhere in the spreadsheet.
I would like to seek advise if it is possible to only run onEdit() when edits are done only on specific ranges, specifically B6:B20 of Main sheet (which contains my checkboxes). Likewise, instead of going over all checkboxes and showing/hiding sheets based on what is ticked/unticked, is it possible for onEdit() to detect what was immediately ticked/unticked from among the checkboxes and show/hide the appropriate sheet? We maybe adding several other sheets in the future, and it will just get more inefficient if we try to scale the current code.
Here's the code:
function onEdit(e) {
var sheet = e.source.getSheetByName("Main");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var A = ss.getSheetByName("A");
var B = ss.getSheetByName("B");
var C = ss.getSheetByName("C");
var D = ss.getSheetByName("D");
var E = ss.getSheetByName("E");
var F = ss.getSheetByName("F");
var G = ss.getSheetByName("G");
var H = ss.getSheetByName("H");
var I = ss.getSheetByName("I");
var J = ss.getSheetByName("J");
var K = ss.getSheetByName("K");
var L = ss.getSheetByName("L");
var M = ss.getSheetByName("M");
var N = ss.getSheetByName("N");
if(sheet.getRange('B6').getValue() === true){A.showSheet();}else{A.hideSheet();}
if(sheet.getRange('B7').getValue() === true){B.showSheet();}else{B.hideSheet();}
if(sheet.getRange('B8').getValue() === true){C.showSheet();}else{C.hideSheet();}
if(sheet.getRange('B9').getValue() === true){D.showSheet();}else{D.hideSheet();}
if(sheet.getRange('B10').getValue() === true){E.showSheet();}else{E.hideSheet();}
if(sheet.getRange('B11').getValue() === true){F.showSheet();}else{F.hideSheet();}
if(sheet.getRange('B12').getValue() === true){G.showSheet();}else{G.hideSheet();}
if(sheet.getRange('B13').getValue() === true){H.showSheet();}else{H.hideSheet();}
if(sheet.getRange('B14').getValue() === true){I.showSheet();}else{I.hideSheet();}
if(sheet.getRange('B15').getValue() === true){J.showSheet();}else{J.hideSheet();}
if(sheet.getRange('B17').getValue() === true){K.showSheet();}else{K.hideSheet();}
if(sheet.getRange('B18').getValue() === true){L.showSheet();}else{L.hideSheet();}
if(sheet.getRange('B19').getValue() === true){M.showSheet();}else{M.hideSheet();}
if(sheet.getRange('B20').getValue() === true){N.showSheet();}else{N.hideSheet();}
}
Likewise, here's a preview of our Main sheet. Based on the image, sheets B, E, J and K should be showing while the others should be hidden:

onEdit will fire anytime the sheet is edited, however, you can reference the event object (e) to determine which cell was modified, and only take action if the edit is on a cell you actually care about.
I would do this by creating a map of cells to sheets, so you don't need a bunch of if statements, but rather just check if the cell is mapped and take action if it is.
var actionable_cell_map = {'B6':'A',
'B7':'B',
...
'B20':'N'
};
function onEdit(e){
if(e.range.getSheet().getName() == "Main"){
var edited_cell = e.range.getA1Notation();
if(actionable_cell_map[edited_cell]){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(actionable_cell_map[edited_cell]);
if(e.range.getValue() === true){
sheet.showSheet();
}else{
sheet.hideSheet();
}
}
}
}

Related

automatically move to the next cell on the page

I have a spreadsheet I designed in Google Sheets to input data at work and then a formula that determines if the part needs to be replaced and provides the part number required. I need either a macro or appScript that will start with a certain cell on the same sheet, highlight it, allow me to type a value in it, then either by pressing the ENTER or TAB key to move to the next cell on the page (Not necessarily the next door cell, but a cell in another column and/or row), -AND- based on a data validation check box determine which cells are selected. How do I write either a macro or appScript to do what I need? Which would be easier?
Reference
sheet.setActiveSelection()
Script:
Try
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'mySheetName'){ // adapt
var addresses = ["E7","H7","E10","H10","E13","H13","E16"]; // adapt
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]);
}
}
}
Note:
This way you can determine the order in which the cells are selected.
If you have a script that copies cells into a master data sheet, you can take advantage of the range list. (by the way, you can find here how to transfer the data).
In case of protected sheet:
If your sheet is protected, except for the cells that need to be filled in, you can use a script that will search for them and reorganize them.
function onEdit(event) {
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'mySheetName') { // adapt
var addresses = listOfUnprotectedRanges()
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1) {
sh.setActiveSelection(addresses[item + 1]);
}
}
}
function listOfUnprotectedRanges() {
var p = SpreadsheetApp.getActiveSheet().getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
var ranges = p.getUnprotectedRanges().map(r => [r.getA1Notation(), r.getRow(), r.getColumn()])
ranges = ranges.sort(function (a, b) { return a[2] - b[2]; }); // sort by columns
ranges = ranges.sort(function (a, b) { return a[1] - b[1]; }); // sort by ranges first
return ranges.map(r => r[0])
}

onSelectionChange re-triggers itself when changing tab

I've been trying to get a simple function to work for a couple of days but I can't see where I'm going wrong. I would really appreciate some help is possible please.
Background:
I have a calendar tab with a table containing Stores down the side and Dates across the top.
When the user clicks between E7:AI200 I want to be able to:
Grab the Store Name from column 1 and the clicked row and populate B1 with the value.
Grab the Date from row 1 and the clicked column and populate C1 with the value.
This seems to work fine.
I also want the spreadsheet to automatically navigate to another tab called 'Filtered Events' when the cell between E7:AI200 clicked.
This also seems to work fine.
Issue: When I click back to the Calendar tab it seems to re-click the original cell and navigates me straight back to the 'Filtered Events' tab and I am stuck in a loop of continously going between tabs each time I try to get back to the Calendar.
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActive();
if (ss.getActiveSheet().getName() == "Calendar") {
var sheet = ss.getSheetByName("Calendar");
var full_range = sheet.getRange("A1:AI200");
var click_range = e.range;
var row_lookup = click_range.getRow();
var col_lookup = click_range.getColumn();
var selected_store = full_range.getCell(row_lookup, 1).getValue();
var selected_date = full_range.getCell(1, col_lookup).getValue();
var populate_store = sheet.getRange("B1");
var populate_date = sheet.getRange("C1");
if (row_lookup > 6 && row_lookup < 250 && col_lookup < 36 && col_lookup > 4) {
populate_store.setValue(selected_store);
populate_date.setValue(selected_date);
var change_sheet = ss.getSheetByName("Filtered Events");
change_sheet.activate();
}
else {
populate_store.setValue('');
populate_date.setValue('');
}
}
}
Probably you can use PropertiesService to store the last used coordinates of selected cell and check them at the start of the script:
function onSelectionChange(e) {
var ss = SpreadsheetApp.getActive();
if (ss.getActiveSheet().getName() == "Calendar") {
var sheet = ss.getSheetByName("Calendar");
var full_range = sheet.getRange("A1:AI200");
var click_range = e.range;
var row_lookup = click_range.getRow();
var col_lookup = click_range.getColumn();
// try to get the last used cell coordinates
var last_cell_row = PropertiesService.getScriptProperties().getProperty('last_cell_row');
var last_cell_col = PropertiesService.getScriptProperties().getProperty('last_cell_col');
// save current cell coordinates
PropertiesService.getScriptProperties().setProperty('last_cell_row', row_lookup);
PropertiesService.getScriptProperties().setProperty('last_cell_col', col_lookup);
// if the coordinates are the same break the function
if (row_lookup == last_cell_row && col_lookup == last_cell_col) return;
// the rest of your code ...
I haven't tested it. It would be better if you provide some dummy data for testing, how your sheets look like.

Need trigger to run when change occurs within cell of a certain range

I have a spread sheet where users can update a cell's within Column 'J' with notes, however when they update that cell I need my script to run.
This will then copy all the information from Columns 'I' & 'J', and paste them into another cell, then put a Vlookup formula back into 'J' where it has just taken the information from.
It may sound a complicated way about going around things, but because I have various sheets pulling information from different workbooks I need it to happen in this way.
However I have tried numerous solutions and can not get it to run when a cell is changed.
I have tried a installable on change trigger, but this works on the whole workbook and often ends up deleting the information it should be copying.
I have pasted the code I am working in below, any help would be grateful.
function onChange(e)
{
var editRange = { // J:J
top : 2,
bottom : 2000,
left : 10,
right : 10
};
// Exit if we're out of range
var thisRow = e.range.getRow();
if (thisRow < editRange.top || thisRow > editRange.bottom) return;
var thisCol = e.range.getColumn();
if (thisCol < editRange.left || thisCol > editRange.right) return;
//Where Is The Data Stored That You Want To Copy
var ss = SpreadsheetApp.openById("1ZQLKnCHD9acTXCKpTeOnJshHEz43FDPdKZ-U6Me-k_k")
var West = ss.getSheetByName("West Orders")
var WestNotes = ss.getSheetByName("West Notes")
//Ranges of The Data You Want To Copy
var CopyWestNotes = West.getRange(2, 9,West.getLastRow(),2).getValues()
//Clear The Data Before Overiding
var ClearNotes = WestNotes.getRange(2,1,WestNotes.getLastRow(),2).clear()
//Ranges Of The Data You Want To Store
var PasteWestId = WestNotes.getRange(2,1,West.getLastRow(),2).setValues(CopyWestNotes);
//Put a formula in to update Notes;
var formula = '=IFerror(VLOOKUP(i2,\'West Notes\'!A:B,2,false)," ")'
var WestFormula = West.getRange(2,10,West.getLastRow(),1).setValue(formula)
}
The onEdit event
The change trigger is not a simple trigger. You have to install it yourself. There is also no e.range in this event object. I would guess that if you had checked your executions that you would see some errors.
I modified your code to an onEdit() simple trigger. But you may have to make it installable if you're doing anything that requires permissions. And it looks like you are. So now you should be able to continue debugging your code.
function onEdit(e) {
var editRange = {top:2,bottom:2000,left:10,right:10};
var thisRow=e.range.rowStart;
if (thisRow<editRange.top || thisRow>editRange.bottom)return;
var thisCol=e.range.columnStart;
if (thisCol<editRange.left || thisCol>editRange.right)return;
var ss = SpreadsheetApp.openById("id");
var West = ss.getSheetByName("West Orders");
var WestNotes = ss.getSheetByName("West Notes");
var CopyWestNotes = West.getRange(2, 9,West.getLastRow(),2).getValues();
var ClearNotes = WestNotes.getRange(2,1,WestNotes.getLastRow(),2).clear();
var PasteWestId = WestNotes.getRange(2,1,West.getLastRow(),2).setValues(CopyWestNotes);
var formula = '=IFerror(VLOOKUP(i2,\'West Notes\'!A:B,2,false)," ")';
var WestFormula = West.getRange(2,10,West.getLastRow(),1).setValue(formula);
}
I should have mentioned that you'll need to add the id in line 7.
A simple change to line 7 of the script has eradicated the problem, instead of using .openById, I used .getActiveSpreadsheet

Properly scripting onEdit

I have a Google Sheets file with 3 spreadsheets. Each of the first 2 contains numerous number data entries. All I want to do is change one cell value on the third (tt) if any data is changed on a different sheet. Looking at my incomplete onEdit() script, you will see I obviously don’t know what I’m doing. How to I change the value of cell ‘a5’ on third spreadsheet (tt) for any changes made anywhere on only the first two?
function onEdit(event)
{
var aa = event.SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Input-Expenses");
var ss = event.SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Input-Income-n-Assets");
var tt = event.SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Run Program");
var r = event.aa.getActiveRange('a1:r105');
var s = event.ss.getActiveRange('d1:q94');
var g = 0
tt.getRange('a5').setValue(g);
}
You need to refer to this article:
https://developers.google.com/apps-script/guides/triggers/
Find Edit trigger.
range A Range object, representing the cell or range of cells that
were edited.
Usage:
var editedRange = event.range;
var editedSheet = editedRange.getSheet();
if (editedSheet.getName() === 'Sheet1')
{
var targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
// do smth
}
This should do what you want
function onEdit(event) {
try {
var aName = event.range.getSheet().getName(); // Active sheet
if( ( aName === "Input-Expenses" ) || ( aName === "Input-Income-n-Assets" ) ) {
// You said anywhere on these 2 sheets and put in Run Program:A5
event.source.getSheetByName("Run Program").getRange("A5").setValue(0);
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}

How to allow shared users in google sheets to run script that accesses protected sheet [duplicate]

This question already has an answer here:
How to allow onEdit function to affect protected cell in a Google Sheet?
(1 answer)
Closed 11 months ago.
I have a google spreadsheet in which I plan to share with over 50 users who will each have their own sheet. For security measures, I have some script that I would like to run which would allow a user to enter data into their sheet but prevent them from deleting that entry after. The code works fine on my end, but when I tried to test it out by sharing it to one of the users, the script either didn't run or is not allowed to run.
I have done my research on this matter for a while now and cannot seem to apply any of the solutions that I have seen posted on this forum and many others. I am using an onEdit() function which, to the best of my knowledge, is a simple trigger so it shouldn't cause this kind of error. The code is as follows:
function onEdit(event) {
var masterSheetName = "Blank" // sheet where the cells are protected from updates
var helperSheetName = "Blank Copy" // sheet where the values are copied for later checking
// range where edits are "write once": D18:Y157, i.e., rows 18-157 and columns 4-25
var firstDataRow = 18; // only take into account edits on or below this row
var lastDataRow = 157; // only take into account edits on or above this row
var firstDataColumn = 4; // only take into account edits on or to the right of this column
var lastDataColumn = 25; // only take into account edits on or to the left of this column
var miscFirstDataColumnOne = 15; // only take into account edits on or to the right of this column
var miscLastDataColumnOne = 15; // only take into account edits on or to the left of this column
var miscFirstDataColumnTwo = 25; // only take into account edits on or to the right of this column
var miscLastDataColumnTwo = 25; // only take into account edits on or to the right of this column
var miscFirstDataRowTwo = 18;
var miscLastDataRowTwo = 157;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var masterSheet = ss.getActiveSheet();
var masterSheetMiscOne = ss.getActiveSheet();
if (masterSheet.getName() != masterSheetName) return;
if (masterSheetMiscOne.getName() != masterSheetName) return;
var masterCell = masterSheet.getActiveCell();
var masterCellMiscOne = masterSheetMiscOne.getActiveCell();
if (masterCell.getRow() < firstDataRow || masterCell.getColumn() < firstDataColumn ||
masterCell.getRow() > lastDataRow || masterCell.getColumn() > lastDataColumn) return;
var helperSheet = ss.getSheetByName(helperSheetName);
var helperCell = helperSheet.getRange(masterCell.getA1Notation());
var newValue = masterCell.getValue();
var oldValue = helperCell.getValue();
var user = SpreadsheetApp.getActive().getEditors()[1];
var permission = helperSheet.getSheetProtection();
permission.addUser(user);
helperSheet.setSheetProtection(permission);
SpreadsheetApp.flush();
if (oldValue == "") {
helperCell.setValue(newValue);
} else {
masterCell.setValue(oldValue);
Browser.msgBox('You can not delete this value');
}
if ((masterCellMiscOne.getRow() < firstDataRow || masterCellMiscOne.getColumn() < miscFirstDataColumnOne || masterCellMiscOne.getRow() > lastDataRow ||
masterCellMiscOne.getColumn() > miscLastDataColumnOne) & (masterCellMiscOne.getRow() < firstDataRow || masterCellMiscOne.getColumn() < miscLastDataColumnOne ||
masterCellMiscOne.getRow() > lastDataRow || masterCellMiscOne.getColumn() > miscLastDataColumnTwo)) return;
var miscCellValueOne = masterCellMiscOne.getValue();
if (miscCellValueOne !== "") {
Browser.msgBox('Submission Needs To Be Authorized Before Being Added.');
}
SpreadsheetApp.flush();
permission.removeUser(user)
helperSheet.setSheetProtection(permission)
}
}
Each user has a sheet which will also have a copy, (in this code the users sheet is "Blank" and the copy is "Blank Copy"). The blank copy will be protected so they can not edit this because it will allow them to delete the data in their sheet (Blank). This code does work, but I just need for it to work when I share the spreadsheet.
All help is greatly appreciated and here is a link to a copy of the spreadsheet.
https://docs.google.com/spreadsheet/ccc?key=0AhBLjhwt88kUdFZ2cG9CVFNEQy1zVHdJYlp6ZEx5Unc&usp=sharing
Short answer: Create a simple add-on with minimal code refactoring
Explanation
... but when I tried to test it out by sharing it to one of the users, the script either didn't run or is not allowed to run.
Unfortunately this would be the case as the "trigger" that you're currently using (i.e. the onEdit one) -
... cannot access services that require authorization.
Please refer to Simple Triggers > Restrictions.
Alternate solution
You can introduce an onInstall function and publish this code as an add-on with visibility set to Private if folks from only your domain are going to use the sheet/script.
Hope this helps.