My script below works as an independent script - It will add a date and a time in a cell/column opposite the trigger cell/ column. I.e. Any cell in Column A stating TRUE will have in its adjacent Column B cell stating the exact date & time, at any moment the cell in Column A is deemed to be TRUE. The script keeps this as as one single change. If I were to delete TRUE or change it to any other text, the data in column B would remain the same, until I write TRUE again - This will force the cell(s) in Column B to overwrite the previous date/time with the latest date/time the moment the new "TRUE" statement was issued in Column A.
Now, I would like to add another onEdit function where it works on Sheet 2, but the column reference numbers are not 1 (as you will see below, all numbers are 1, whether positive or negative), but will be 2. I do not know how to get the two separate functions to work as when I list them separately, with an identical script structure, only the last script in the sequence works, and the first one does not.
I hope I have made this clear enough; I am not a coder by any means, and I have done my best with tutorials, forums, etc before posting this question. I can learn better by reverse-engineering someone else's work rather than being given the building blocks and told to create something, but I appreciate any education on this so I can learn for the future.
function onEdit(e) {
var aCell = e.source.getActiveCell(), col = aCell.getColumn();
if(col == 1) { //number of column(s) where you have a data
var adjacentCell = aCell.offset(-1,-1);
var newDate = Utilities.formatDate(new Date(),
"GMT+1", "dd/MM/yyyy hh:mm:ss");
adjacentCell.setValue(newDate);
}}
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
//change to be your sheet name so the script only triggers on that sheet
if( ss.getName() == "Sheet1" ) { //checks that we're on the correct sheet
var r = ss.getActiveCell();
if(e.value != "TRUE") return;
e.source.getActiveSheet().getRange(e.range.rowStart,e.range.columnStart+1).setValue(new
Date());
}
}
I don't think that you have specified sufficient conditions for this to work correctly but this is what you have so far:
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet1' && e.range.columnStart == 1) {
e.range.offset(-1,-1).setValue(Utilities.formatDate(new Date(),"GMT+1", "dd/MM/yyyy hh:mm:ss"));
}
if(sh.getName() == 'Sheet1' && e.value == "TRUE") {
e.range.offset(0,1).setValue(new Date())
}
}
I imagine you will need further debugging to get this to work.
Related
I'm new to Google App Script and trying to create a queue-like spreadsheet.
The current design is that: in sheet A, there's a column of checkboxs, and if any one of them is checked, the entire row would be moved to the end of another sheet, say sheet B.
The way I calculate the end of the sheet B is as followed:
(I read from a different stackoverflow post)
var Avals = dest.getRange("B:B").getValues();
var Alast = Avals.filter(String).length;
var lastRow = String(Alast + 1);
The issue I'm having here is that when I check two checkboxes quickly one by one, they are mapped to the same lastRow in sheet B, because the second checkbox triggers the onEdit trigger before the first one has moved the entire row to the bottom of the sheet B.
Does anyone know how to solve this issue?
Right now, the workaround is since it's a shared google sheet, I asked my colleagues not to click the checkbox column at the same time.
This seems to be working for what little testing I've done
function onEdit(e) {
LockService.getScriptLock().tryLock(2000)
const sh = e.range.getSheet();
if(sh.getName() == "Sheet0" && e.range.columnStart == 1 && e.range.rowStart > 1 && e.value == "TRUE") {
let tsh = e.source.getSheetByName("Sheet1");
let vs = sh.getRange(e.range.rowStart, 1, 1,sh.getLastColumn()).getValues();
tsh.getRange(tsh.getLastRow() + 1, 1 , vs.length, vs[0].length).setValues(vs);
}
LockService.getScriptLock().releaseLock();
}
I am attempting to have my trigger do multiple actions in my google sheet after an automated form from slack inputs a row into my sheet. The actions that I want it to do are:
insert todays date in a certain column
Fill formula down on column k
Fill formula down on column l
Fill formula down on column m
So far I am only getting the first behavior - Here is my code:
function setUpTrigger() {
ScriptApp.newTrigger('onEdit')
.forSpreadsheet('1QOx3H-NsK52bojXv1J-bq-9jt2ZJKG8RaiiAdcDAx4w')
.onChange()
.create();
}
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Sheet1" ) { //checks that we're on Sheet1 or not
var r = s.getActiveCell();
if( r.getColumn() == 1 ) { //checks that the cell being edited is in column A
var nextCell = r.offset(0, 1);
if( nextCell.getValue() === '' ) //checks if the adjacent cell is empty or not?
nextCell.setValue(Utilities. formatDate(new Date(), "GMT-5:00", "''MM-dd-yyyy"))
var lr = s.getLastRow();
var fillDownRange1 = s.getRange(2,11,lr-1);
var fillDownRange2 = s.getRange(2,12,lr-1);
var fillDownRange3 = s.getRange(2,13,lr-1);
s.getRange(K2).copyTo(fillDownRange1);
s.getRange(L2).copyTo(fillDownRange2);
s.getRange(M2).copyTo(fillDownRange3)
} }
}
How do I get the values to fill down from my formulas when new rows are added?
`
I noticed that I get two entries in my executions log with that kind of a trigger. The first is a simple trigger and the second is an installable trigger. And I also get inconsistent results. Sometimes the function I wrote works and sometimes the code keeps on running until it times out. Personally, I'd avoid doing such a thing. It's okay to create an onChange installable trigger but don't name it onEdit(e) because that's the default for a simple onEdit trigger. At first I thought it might be an interesting way to get e.range when using an onChange trigger but I think it would cause more problems than it's worth.
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.
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
}
I have the below script working fine on my "Order sheet" (project name is "Order sheet code" file is "Code.gs")
function onEdit(e) {
var sheet = e.source.getActiveSheet();
var activeCell = sheet.getActiveCell();
var col = activeCell.getColumn();
var row = activeCell.getRow();
if (e.range.getSheet().getSheetName() == 'Order Sheet') {
if (col == 6) { // assuming status is in column 6 (E), adapt if needed
if (col == 6 && sheet.getRange(row, col + 1).isBlank())
sheet.getRange(row, col + 1).setValue(new Date()).setNumberFormat('DDD, dd MMM'); // change the display format to your preference here
}
if (col == 6 && sheet.getRange(row, col).isBlank())
sheet.getRange(row, col + 1).setValue("")
}
}
Now I have a new sheet, "Work Hours" where I want to utilize the same sort of functionality (different column and time instead of date), but even when I copy this script to a new script file (Project "Work Hours code", file name "Code.gs") and change the SheetName (line 7) to 'Work Hours' it doesn't seem to work.
I thought maybe it was because I had 2 projects with the Code.gs file, but changing that to Code2.gs didn't make a difference (changed it back, just in case).
Also, eventually I want this to run even when the sheet is closed, because I need it to record when data comes in from an external source. Is that possible? I presume I'd have the change the variable for "sheet" since it wouldn't be active?
This will work on both sheets:
function onEdit(e) {
var sh= e.range.getSheet()
var shts=['Order Sheet','Work Hours']
if (shts.indexOf(sh.getName()!=-1)) {
//do whatever you want in here
}
}
or if want to do different things on each sheet:
function onEdit(e) {
var sh= e.range.getSheet()
if(sh.getName() == 'Order Sheet') {
}
if(sh.getName() == 'Work Order') {
}
}
Ok, for other newbies like me with this issue:
BE EXTRA VIGILANT... I know this is obvious, but especially with copy/paste: it seems I had an extra { in my original script...
Probably happened when copy-pasting and deleting a few times when I was trying different things.
I also found why I couldn't make #Cooper 's suggestion work: I was working on BOUND SCRIPTS (ie scripts bound to a specific sheet)