onSelectionChange for a specific Google Sheets tab - google-apps-script

Here is my test sheet.
Goal: whenever I click on cells A5:A10 in 'Sheet 1', I want the value of A1 to change to B5:B10.
For example: if I click A7, A1 = B7.
Note: I don't want this script to run for any other sheet or document.
Can you please help me create a script to run automatically for this purpose?

Explanation:
Indeed, the onSelectionChange(e) trigger is what you are looking for.
You just need to take advantage of the event object to capture information of the selected cell.
When you click on a particular cell in range A5:A10 of Sheet1 the following script will update the value of cell A1 to the corresponding value of B5:B10.
What is important here is to understand the if condition I used:
if (as.getName() == 'Sheet1' && row>4 && row<11 && col==1)
Essentially, I am asking for selections only in Sheet1, after row 4 and before row 11 and column 1. That is basically the range A5:A10.
Solution:
function onSelectionChange(e) {
const as = e.source.getActiveSheet();
const row = e.range.getRow();
const col = e.range.getColumn();
if (as.getName() == 'Sheet1' && row>4 && row<11 && col==1){
as.getRange('A1').setValue(as.getRange(row,2).getValue());
}
}
You could also use offset to get the value of the next column instead of hardcopying the number 2.
Replace:
as.getRange('A1').setValue(as.getRange(row,2).getValue());
with:
as.getRange('A1').setValue(e.range.offset(0,1).getValue());
but both approaches work just as fine.

As an alternative to what Marios suggests, I prefer exiting as early as possible (since the onSelectionChange can fire very rapidly, I find it somewhat more performant). So, you can move your check to the top of the function (the rest still apply):
function onSelectionChange({ range }) {
const sh = range.getSheet();
const shname = sh.getSheetName();
if( shname !== "<sheet name here>" ) { return; }
//continue if ok
}
Note that usually, it is better to put the sheet name in a configuration object (or, even better, in a function that returns a configuration object) for easy maintenance.
Also, since each sheet has a unique Id (you can visually find it in the gid anchor of the open spreadsheet URL or programmatically with the method mentioned below), you could save you some trouble if the sheet gets renamed and check for id match instead with getSheetId:
function onSelectionChange({ range }) {
const sh = range.getSheet();
const id = sh.getSheetId();
if( id !== 123456789 ) { return; }
//continue if ok
}

Related

Googlesheet dropdown list to show/hide rows

I want to have various cells that have a dropdownlist to show/hide certain rows
I have the following table
If I select A2 (dropdownlist with AMERICAS_a and AMERICAS_s), with the _s option, I want to hide only rows 3 to 6
If I select A7 (dropdownlist with EUROPE_a and EUROPE_s), with the _s option, I want to hide only rows 8 to 12
I am using an Appscript which works only for 1 of the continent. I want to make it work for any of the Watchcell
function onEdit(a) {
var watchSheet = "Sheet1";
var watchCell = "A2";
var sheet = a.range.getSheet();
if (sheet.getName() !== watchSheet ||
a.range.getA1Notation() !== watchCell) {
return;
}
sheet.hideRows(2, 5);
switch (a.value) {
case "AMERICAS_a":
sheet.showRows(2, 5);
break;
case "AMERICAS_s":
sheet.showRows(2, 1);
break;
default:
}
}
But I am unsure how to add A7 as another watchcell. I have repeated the function onEdit(e) but only the last onEdit function works.
I believe your goal is as follows.
Your Spreadsheet has the data validation rules for several cells in the column "A".
When the value of the dropdown list is changed, you want to run the script.
For example, when the suffix letters of the value of the dropdown list are _s, you want to hide rows until the next dropdown list.
For example, when the suffix letters of the value of the dropdown list are _a, you want to show rows until the next dropdown list.
Modification points:
In your showing script, in the case of both values of AMERICAS_a and AMERICAS_s, the rows are shown.
From your question, I thought that when the position of data validation rules is retrieved, the script might become simple.
When these points are reflected in a sample script, it becomes as follows.
Modified script:
function onEdit(e) {
const sheetName = "Sheet1"; // Please set the sheet name.
const { range, value } = e;
const sheet = range.getSheet();
const row = range.rowStart;
const datavalidations = sheet.getRange("A1:A" + sheet.getLastRow()).getDataValidations();
if (sheet.getSheetName() != sheetName || !datavalidations[row - 1][0]) return;
datavalidations.splice(0, row);
const n = datavalidations.findIndex(([a]) => a);
sheet[value.slice(-2) == "_s" ? "hideRows" : "showRows"](row + 1, n > -1 ? n : datavalidations.length);
}
Testing:
When this script is used, the following result is obtained.
Note:
This sample script is for your sample showing Spreadsheet. So, when you change the structure of the Spreadsheet, this script might not be able to be used. Please be careful about this.
When this script is directly run with the script editor, an error like TypeError: Cannot destructure property 'range' of 'e' as it is undefined. occurs. Please be careful about this. Please change the dropdown list of the column "A".
References:
Simple Triggers
hideRows(rowIndex, numRows)
showRows(rowIndex, numRows)

Why can't my shared users use my script I have written into the Google Sheet?

I have made a Google Sheet for job tracking. I wrote a script to hide certain rows once a field is entered into a specific cell.
This is the script:
function myFunction() {
}
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("PRODUCTION TRACKER"); // Enter sheet name
var row = s.getRange('AK:AK').getValues(); // Enter column letter that has
//the text "hide" and "unhide"
s.showRows(1, s.getMaxRows());
for(var i=0; i< row.length; i++){ if(row[i] == 'YES') { s.hideRows(i+1, 1); } //Value to hide
}}
It's very simple and works just fine for me. But why does it not work for my Shared users? This script is something that is done automatically once a cell has a certain value, it's not a button or anything.
Explanation:
It seems that your code does not match the description you have given at the last comment. Basically, this is what your code does:
For every cell edit, it shows all the rows, even if they were previously hidden, then the script rechecks each row in column AK for the value 'YES', and hides those rows one by one.
Given your use case, the script could execute a lot of sheet changes, i.e. hideRows(i+1,1) for each cell edit. Sheet changes are very slow and greatly impact execution time.
You might have reached the quota of 90 min / day for triggers. Maybe not in your account, but in your shared users'.
Solution:
Unless there's a need for this specific behavior, you can optimize the code by utilizing the event object in the function:
function onEdit(e) {
var s = e.range.getSheet();
if (s.getName() == "PRODUCTION TRACKER" &&
e.range.getColumn() == 37 &&
e.value == "YES") {
s.hideRows(e.range.getRow());
}
}
Note that you can still use the original code, but not inside a trigger.

Clear columns of selected cell row?

I want to clear contents of selected row (not delete entire row). Can someone help?
Eg: D3 is selected
Clear D3,E3,F3,G3,H3 but not delete the row
Explanation:
You are looking for the onSelectionChange(e).
The following script will clear D:H of the row/cell you selected in column D.
Code snippet:
function onSelectionChange(e) {
const sheetName = "Sheet1"; // put the name of your sheet here
const range = e.range;
const sheet = range.getSheet();
if(sheet.getName()==sheetName && range.getColumn() == 4){
sheet.getRange(range.getRow(),4,1,5).clearContent();
}
}
I hope it is clear that this is a trigger function. Namely, you are not supposed to manually execute it by yourself but it will be triggered upon selections in the sheet (see Illustration).
Illustration:
Possible Restriction:
onSelectionChange is a little bit buggy but it can work really well. If it does not work in the beginning, disable the v8 run time environment, refresh the script editor page and then enable v8 environment again. You might need to wait or refresh the page again but it will eventually work. Please read the related issue here.
Simply select the cells the you wish to clear and hit backspace to clear the contents of the cells.
Alternatively, run this function in the script editor:
function clearData(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('THE_NAME_OF_YOUR_SHEET').getRange('D3:H3');
ss.clear();
}
function Deleterow() {
const sheetName = "Anju"; // put the name of your sheet here
var range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveCell();
const sheet = range.getSheet();
if(sheet.getName()==sheetName && range.getColumn() == 4){
sheet.getRange(range.getRow(),4,1,5).clearContent();
}
}

Apps Script to update Timestamp when data is inserted automatically in google sheet

This code works fine when data is edited in Column 3 or being copy-pasted but if the cursor remains at column 1 at the time of the whole row being copy/pasted, it won't update and secondly, if salesforce sends data to column 3, it doesn't work that time too, please help me here.
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
var sName = s.getName();
var r = s.getActiveCell();
var row = r.getRow();
var ar = s.getActiveRange();
var arRows = ar.getNumRows()
// Logger.log("DEBUG: the active range = "+ar.getA1Notation()+", the number of rows = "+ar.getNumRows());
if( r.getColumn() == 3 && sName == 'Sheet1') { //which column to watch on which sheet
// loop through the number of rows
for (var i = 0;i<arRows;i++){
var rowstamp = row+i;
SpreadsheetApp.getActiveSheet().getRange('F' + rowstamp.toString()).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm"); //which column to put timestamp in
}
}
}//setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm:ss");
Explanation:
Three important things to know:
As it is also stated in the official documentation, the onEdit triggers are triggered upon user edits. This function won't be triggered by formula nor another script. If salesforce or any other service except for the user, edits column C the onEdit trigger is not going to be activated. Workarounds exist, but these workarounds depend on the context of your specific problem. I would advice you to search or ask a specific question about it.
Regarding the other issue you have, you should get rid of active ranges and take advantage of the event object. This object contains information regarding the edit/edits user made.
As it is recommended by the Best Practices you should not set values in the sheet iteratively but you can to that in one go by selecting a range of cells and set the values. In your case, you want to set the same value in all of the cells in the desired range, hence setValue is used instead of setValues. But the idea is to get rid of the for loop.
Solution:
function onEdit(e) {
var s = e.source.getActiveSheet();
var sName = s.getName();
var ar = e.range;
var row = ar.getRow();
var arRows = ar.getNumRows()
if( ar.getColumn() == 3 && sName == 'Sheet1') {
s.getRange(row,6,arRows).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm");
}
}
Note:
Again, onEdit is a trigger function. You are not supposed to execute it manually and if you do so you will actually get errors (because of the use of the event object). All you have to do is to save this code snippet to the script editor and then it will be triggered automatically upon edits.

Timestamp script for multiple columns (Specific ones) for multiple tabs in one sheet

I am trying to find a script that add a timestamp whenever any changes happen in specific columns. For example any changes happen in column G "Status" and column J "No. of follow up calls" will be reflected in Column N & O "Time Stamp for new Prospects" "Time Stamp for follow up calls".
To watch col= 7, 10
To timestamp= 14,15
And all of these will be made in certain tabs in the same google sheet.
Solution:
You are looking for an onEdit() trigger.
The following code checks if the edited sheet belongs to onEditSheets using the includes() method. Please change part of the code to include the sheets/tabs you want this function to interact with:
const onEditSheets = ['Sheet1','Sheet5'];
Then it adds a timestamp whenever there is a change in the respective
columns you mentioned in your question.
function onEdit(e) {
const row = e.range.getRow();
const col = e.range.getColumn();
const onEditSheets = ['Sheet1','Sheet5'];
const as = e.source.getActiveSheet();
const columns = as.getRange(1,1,1,as.getMaxColumns()).getValues().flat();
const columns_n = ['Status','No. of follow up calls','Time Stamp for new Prospects','Time Stamp for follow up calls'];
const columns_i = columns_n.map(c=>columns.indexOf(c)+1);
if(onEditSheets.includes(as.getName()) && col == columns_i[0] && row > 1)
{
as.getRange(row,columns_i[2]).setValue(new Date());
}
if(onEditSheets.includes(as.getName()) && col == columns_i[1] && row > 1)
{
as.getRange(row,columns_i[3]).setValue(new Date());
}
}
Instructions:
Click on Tools => Script editor:
Copy and Paste the aforementioned code snippet to an empty .gs file
in the script editor and click save: