Google Sheets getActiveRange on change - google-apps-script

I have a function starting off as such
function onEdit2(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// Returns the active cell
var activeRange = sheet.getActiveRange();
Logger.log(activeRange.getA1Notation());
I have this function triggered 'on change' in a spreadsheet (it was originally on edit, hence the name, but that isn't triggered by changing background color, which is kinda critical to what I'm doing with this).
This always writes A1 to the logger, no matter what part of the spreadsheet I have selected, and it does the same thing with getActiveCell in place of getActiveRange.
Is there any way to get the selected range when a script runs?

This is a serious drawback with this new trigger type. There has already been an issue raised about it:
Issue 2751: SpreadSheet onChange() trigger
Visit & star the issue to receive updates. It would be helpful to add your own observations as well, since the original issue has been entered as an enhancement request, but the fact that the trigger doesn't know the changed range is bug-flavoured.

Related

Google Sheets App Script Edit Protected Cell or Object Error

I have a Google Sheets script that I've been using for the past 2+ years that grabs a chunk of data and transcribes it into a pair of tally sheets, then clears the cells that had been filled creating said chunk of data so the process can be started over. This is used for an inventory calculation system. The script has reliably worked up until roughly 3 weeks ago, but I now encounter an edit protection error when users with limited access to the sheet attempt to run the script. None of the editable cells/ranges reference in the script are locked to any user, but the sheet does have protection on cells I do not want anybody to make inadvertent changes to.
The script is:
function CopyErADD() {
var sss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = sss.getSheetByName('ADDER'); //Entry Sheet
var rangeIn = sheet.getRange('B32:N45'); //Range to copy into "Incoming" sheet
var dataIn = rangeIn.getValues();
var ts = sss.getSheetByName('Incoming'); //Tally sheet in
ts.getRange(ts.getLastRow()+1, 1, dataIn.length, dataIn[0].length).setValues(dataIn);
var rangeOut = sheet.getRange('B48:O54'); //Range to copy into "Outgoing" sheet
var dataOut = rangeOut.getValues();
var tss = sss.getSheetByName('Outgoing'); //Tally sheet out
tss.getRange(tss.getLastRow()+1, 1, dataOut.length, dataOut[0].length).setValues(dataOut);
SpreadsheetApp.flush() // NEWLY ADDED PER METAMAN'S SUGGESTION
sheet.getRange('E2:E5').clearContent();
sheet.getRange('B7:B20').clearContent();
sheet.getRange('E7:H20').clearContent();
sheet.getRange('I7:I20').setValue('kg');
sheet.getRange('L7:L20').clearContent();
sheet.getRange('B24:B29').clearContent();
sheet.getRange('J24:J29').clearContent();
}
I also have an "erase only" script that runs the second second part of the script only (if data has been entered incorrectly) that executes perfectly fine. It is only when coupled with the copy/transcribe portion of the script that the protection error occurs.
function ErADD() {
var sss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = sss.getSheetByName('ADDER');
sheet.getRange('E2:E5').clearContent();
sheet.getRange('B7:B20').clearContent();
sheet.getRange('E7:H20').clearContent();
sheet.getRange('I7:I20').setValue('kg');
sheet.getRange('L7:L20').clearContent();
sheet.getRange('B24:B29').clearContent();
sheet.getRange('J24:J29').clearContent();
}
Commenting out the sheet.getRange.... etc clearContent and setValue portion of the first script allows it to complete successfully, so I attempted to create a master function that calls the CopyErAdd script (sans clear portion) and then calls the ErADD script, and I encounter the same error. Both script can be run on their own successfully, but when combined the erase portion encounters the error.
Does anybody see any issues that I am missing, or did something occur over the past few weeks that I'm not aware of that could cause this protection error?
I appreciate any ideas anybody might have.
Edit - Thank you MetaMan for the tips for making the script more efficient.
As for the protection, "Incoming" and "Outgoing" copy destination sheets are completely open, no protection at all. "ADDER" sheet is protected except certain cells that are edited by user, and are referenced in the script.
E2:E5 (actually E2:I5 open, since the macro button sits on top of the F2:I5 range), B7:B20, E7:I20, L7:L20, B24:B29, J24:J29, All unprotected.
Adding the line
SpreadsheetApp.flush()
in between the copy section and clearing section of the code worked (as shown in the updated code snippet). I'm no longer encountering a range protection error.
I really appreciate the fix, but I'm boggled over why I've never needed that line until recently. I guess you don't need flush() until you do.
You could replace this:
sheet.getRange('I7').setValue('kg');
sheet.getRange('I8').setValue('kg');
sheet.getRange('I9').setValue('kg');
sheet.getRange('I10').setValue('kg');
sheet.getRange('I11').setValue('kg');
sheet.getRange('I12').setValue('kg');
sheet.getRange('I13').setValue('kg');
sheet.getRange('I14').setValue('kg');
sheet.getRange('I15').setValue('kg');
sheet.getRange('I16').setValue('kg');
sheet.getRange('I17').setValue('kg');
sheet.getRange('I18').setValue('kg');
sheet.getRange('I19').setValue('kg');
sheet.getRange('I20').setValue('kg');
with this:
sheet.getRange('I7:I20).setValue('kg');
also replace this:
var ss = sss.getSheetByName('ADDER'); //Entry sheet
var sheet = SpreadsheetApp.getActive().getSheetByName('ADDER');
with this:
var sheet = sss.getSheetByName('ADDER);
If you are doing anything immediate after this function with the data then you might wish to add SpreadsheetApp.flush()
I can't comment on the protection since none of that information was provided.

How to check if user has clicked on the link in google sheet using google apps script?

I have been working on providing an google sheets interface for non technical members in my team, where they can see a sheet of info on some members, and one of those fields is a link to accessing that resource on an internal portal. The sheet looks as shown in the image attached.
This is an example sheet of the same type
This sheet will be accessible to these non technical team, who are going to have a look at this sheet, and if they want to work on a certain case, they will click on the link. What I want to ensure is that, when they get redirected to this url, I want to ensure that that particular row gets deleted from the sheet.
My initial problem is that I dont know what trigger should be used for making such a program. The one that seems most likely is a doGet(event) found here
The second one is how to do i catch an event like this and associate it to a particular cell.
A secondary solution could be that of an onClick(event) which would really make things simpler here, but that is not a "simple Trigger"
Note:- The good thing is that each resource is going to be unique
I could really use some ideas, i have drafted a little bit of a pseudo code here for the same, but it seems very hacks, and not computationally feasible:-
function doGet(event){
// code to delete a certain row when they visit the portal
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if (s.getName = "clicker_checker" && check which url redirected){
check which row and cell matches with the redirections url, and delete that.
}
}
Since you are using a spreadsheet, the most suitable trigger for you would be an onSelectionChange one.
This trigger will allow you to detect when a selection has been made in this spreadsheet- more particularly in the cell wanted.
Therefore, you can use a script similar to this:
function onSelectionChange(e) {
var range = e.range;
var sheet = range.getSheet();
var col = range.getColumn(); //if needed
var row = range.getRow(); //if needed
if (sheet.getName() = "clickeer_checker" && other_condition) {
// instructions here
}
}
Reference
Apps Script Triggers.

How are multiple onEdit() functions processed?

If I have a sheet with 20 separate onEdit() scripts running, would any problems arise if 2 unique edits are made at approximately the same time by different users? More specifically:
1) What is the time resolution for an edit to be recorded? For instance, the probability of two people editing at the same millisecond is very small, but perhaps within the same second may not be terribly improbable. If two people edit in the same second, would it fail to recognize these as separate edits? Or can it clearly recognize every edit as a unique event regardless of how closely they happened?
2) If one onEdit() script is triggered and does not finish before another edit occurs, would this cause a failure in the second edit to be properly evaluated?
3) I have about 20 scripts running on a sheet with about 20 tabs and about 7 users on it at any given time. Are there any other problems you foresee that would cause some onEdit() scripts to not work?
My script checks to see if the edit was made in column x on sheet x and if so, copies it to the next sheet and deletes the row from the first sheet. It is currently failing about 30% of the time. I don't know why. It hasn't done this in the past.
My code looks like this:
function onEdit(e){
var edited_range = e.range;
var edited_row = edited_range.getRow();
var activeSheet = e.source.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source_sheet = ss.getSheetByName("N.Arb Pitches");
var target_sheet = ss.getSheetByName("N.Arb In Progress");
var last_row = target_sheet.getLastRow();
var range_values = target_sheet.getRange("A1:A").getValues();
var range_length = range_values.filter(String).length + 1;
var target_range = target_sheet.getRange(range_length,1);
if (activeSheet.getName() == "N.Arb Pitches") {
if (edited_range.getColumn() == 13) {
if (edited_range.getValue().toLowerCase() == "yes") {
target_sheet.insertRowAfter(last_row);
source_sheet.getRange(edited_row,1,1,12).copyTo(target_range);
source_sheet.deleteRow(edited_row) }}};
}
There is only 1 onEdit(e) function in each project, and I have this many projects in my sheet. Each project has a function like the one above but with sheet names changed.
Without any code to review, I assume you have multiple onEdit() methods in your app script project.
You probably have a number of *.gs files defined as follows:
\** onEdit() defined in, for example, script1.gs *\
function onEdit(e) {...}
And another
\** onEdit() defined in, for example, script2.gs *\
function onEdit(e) {...}
If that's the case then you have a problem. Each time you define a method with the same name (within the same apps script project), regardless of which script file its in, the last definition overrides the ones that came before. So of your 20 onEdit() functions, only the last one defined will be valid.
EDIT
Assigning multiple apps script projects to a single spreadsheet with their own onEdit() is viable. However, if the sheet is heavily used by multiple parties those onEdit() methods will likely give rise to concurrency problems. You can try using LockService to mitigate those issues.

Trigger a script when a formula changes a cell value

I'm using a Google script then sends out an email when a certain column in a Google sheet is changed. The information in the cell is either inputted manually, or completes using a formula based on information in other cells.
The script works fine when information is manually entered, but not when the formula runs. I've read up on it and realise that a formula calculation doesn't count as an edit, so how do I get the script to run?
It's currently set up to trigger from the spreadsheet when there's an edit.
Below is the part of my script that covers the column/cell in question.
function sendEmail() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var row = sheet.getActiveRange().getRow();
var cellvalue = ss.getActiveCell().getValue().toString();
if(sheet.getActiveRange().getColumn() == 12)
There's a lot more included in the script so I haven't copied everything onto here. Many thanks in advance.
There is no trigger that can run when a formula changes.
Try figure out, what conditions is your formula depends on:
if it is another cell, entered manually, then use those cell to trigger it's changes with onEdit
if the formula imports data from external source, use random or time functions, you'd better use onTime trigger.
if the formula uses importrange then go to the range you import and see the original range, return to step 1 → 2...

onEdit timestamp based on other sheet

As of now, it appears that Google Spreadsheets does not allow locking of cell ranges but instead locking has to be done on a sheet by sheet basis in its entirety. I would like to share a sheet with another user and have them enter data on Sheet 1. To prevent tampering, I would like the data duplicated on Sheet 2 (locked by me) with a timestamp of the last change made on Sheet 1. I've been playing with this onEdit() function but it does not give an updated time when I edit Sheet 1 but still only if I edit Sheet 2. I just haven't figured out what I'm doing wrong.
function onEdit(e)
{
var ss = e.source.getActiveSheet();
var rr = e.source.getActiveRange();
//comment 2 lines below if you want it working on all sheets, not just on 2nd one
if(ss.getIndex()!= 2)
return;
///
var firstRow = rr.getSheetByName(Sheet2).getRow();
var lastRow = rr.getSheetByName(Sheet2).getLastRow();
//the last modified date will appear in 12th column
for(var r=firstRow; r<=lastRow; r++)
ss.getRange(r, 12).setValue(new Date());
}
Is there another way I could do this?
You cannot use scripts to prevent the other user from tampering, because
he can go to Tools>Script editor and edit your script so that it
becomes idle, then edit Sheet1, then restore your script.
See the issue tracker for this issue. You cannot protect your scripts
from being edited by other users with whom you share at least one worksheet of your spreadsheet.
On the other hand, leaving scripts aside, you can always see
the history of what was edited by whom and when: File>See revision history.
I see a couple of problems. First, you're trying to use .getSheetByName() on a range object, but that method is only supported by spreadsheet objects. You can run .getSheet() on a range and that will return the sheet the range is in. Secondly, formatting: GAS requires standard Javascript which means that your if and for functions need to have {}. This should work for what you want:
onEdit(e){
if(e.range.getSheet().getSheetName()!="Sheet2"){
var row1=e.range.getRow();
var col2=e.range.getColumn();
var row2=e.range.getLastRow();
var col2=e.range.getLastColumn();
var data=e.range.getValues();//the data to be copied
var copySheet=e.source.getSheetByName("Sheet2");//sheet you want to copy to
var copyRange=copySheet.getRange(row1,col1,row2-row1,col2-col1);// the range data will be copied to
var time=new Date();
var timeRange=copySheet.getRange(row,col);//wherever you want the time signature to be.
timeRange.setValue(time);//inserts time stamp into sheet
copyRange.setValues(data);//copies data to the range
}
}
Just replace row and col with the row and column you want to place the time into. It takes the edited range and copies it to the same location on sheet 2 so it only works if sheet 1 is the only sheet to be edited. Otherwise you'll end up with overlapping data. You could also set a time stamp to be adjusted to the range, meaning put its position as something like (row1,col2+5). That would put it 5 columns right of the edited range.