I have a Google spreadsheet where I would like to automatically update one field every time a new row is added by a Zapier automation.
Every time I test the script manually by adding a row or making edits on existing rows, the script fires as expected. However, when Zapier adds a new row to the spreadsheet absolutely nothing happens.
Any idea why and how I can remedy this problem?
This the script which I am trying to use:
function onEdit(e) {
var row = e.range.getRow();
SpreadsheetApp.getActiveSheet().
getRange(row, 10).setValue('My_New_Value');
}
I was able to replicate the behavior you are seeing in your script. I believe that when you add a row outside of the active data range, it does not trigger an edit event. In order to catch those events, you will also need to catch chagne events.
https://developers.google.com/apps-script/reference/script/spreadsheet-trigger-builder#onchange
function onOpen ()
{
var sheet = SpreadsheetApp.getActive();
ScriptApp.newTrigger("myFunctionx")
.forSpreadsheet(sheet)
.onChange()
.create();
}
Looking at the documentation for a change event, you can detect that a row was inserted, but the row number is not returned.
https://developers.google.com/apps-script/guides/triggers/events#change
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
}
You might be able to find the row that was inserted by looking at the last row in the active range.
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
Logger.log(SpreadsheetApp.getActiveRange().getLastRow());
}
It is possible for a change to affect more than one row if you copy and paste into multiple cells. You should loop through all of the rows in your range.
function onEdit(e) {
for(var i=e.range.getRow(); i<= e.range.getLastRow(); i++) {
SpreadsheetApp.getActiveSheet().
getRange(i, 10).setValue(e.range.getA1Notation());
}
}
onChange does trigger every time a new row is added(through sheets API).
function zapOnChange(e){
if(e.changeType !== 'INSERT_ROW') return;//exit if the type of change is not a row insertion
const insertedRow = SpreadsheetApp.getActive().getActiveRange();
//do something with inserted row
}
Important notes:
Sheets API runs asynchronously. onChange is triggered randomly and NOT in the order of insertion.
For eg, Two simultaneous inserts may occur at A2:F2 and A3:F3, But the insertRow at A2:F2 may trigger onChange after insertion of A3:F3. So, at the time zapOnChange() is run, the last row might already be A3:F3. So, you cannot use .getLastRow() to determine the inserted row, but use .getActiveRange()
Sometimes multiple rows are inserted: .getActiveRange().getNumRows() provides number of inserted rows.
Related
I have a Sheet that has the following:
ID ROW STATUS ......
3588053 4 NEW
The Data for ID and ROW are coming from an External Spread Sheet called KDCAlerts
The STATUS filled is changed with an onEdit command (or in this case onMyEdit) that executes when there are changes on KDCLog
The ID and ROW changes each time the External spreadsheet changes (ex: adding / deleting rows, etc.)
What I need to do is to fix this so that when ID or ROW changes, the onMyEdit function is ran.
How can I do this? Below is the code currently being used now.
Any help, hints or advice would be greatly appreciated.
TIA
function creatTrigger() {
if(ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() == "onMyEdit").length == 0) {
ScriptApp.newTrigger("onMyEdit").forSpreadsheet(SpreadsheetApp.getActive()).onEdit().create();
}
}
function onMyEdit(e) {
var sh = e.range.getSheet();
if (sh.getName() == "KDCLog" ) {
var extSS = SpreadsheetApp.openById("1b5qiNxxxxxxxxxRuLf-8dTBgRU9cHLBbd2A");
var extSH = extSS.getSheetByName("KDCAlerts");
} else { return; }
....
....
UPDATE:
#doubleunary - thanks for the response.
It is unclear how the values in the ID and ROW columns get written to the spreadsheet,
There are 2 Files: KDCLog and KDCAlerts.
KDCLog!ID is populated as follows:
=IF ( ISERROR( INDEX(SORTN(FILTER({KDCAlerts!E:E,KDCAlerts!H:H, KDCAlerts!A:A}, KDCAlerts!D:D=E7),1,,2,FALSE),,3) ), -1, INDEX(SORTN(FILTER({KDCAlerts!E:E,KDCAlerts!H:H, KDCAlerts!A:A}, KDCAlerts!D:D=E7),1,,2,FALSE),,3) )
So, it is "pulling" from KDCAlerts using a function (being executed in the KDCLog worksheet).
Whenever the KDCAlerts change, the Value in KDCLog!ID also changes (without manual intervention - the KDCAlerts changes with the adding of rows).
If you are using another script to write to the spreadsheet, it is
likely that no events get sent.
For the STATUS column, it is populated with an onEdit funciton (seen above)
trigger events that can be monitored with an installable on change
trigger.
Is there a sample on how this could work
You cannot use on edit trigger here, because it fires only when the spreadsheet is manually edited by a user, regardless of whether you are using a simple trigger or an installable trigger.
It is unclear how the values in the ID and ROW columns get written to the spreadsheet, which is what actually determines whether you can catch those updates. If you are using another script to write to the spreadsheet, it is likely that no events get sent.
If you are using another integration tool, it may trigger events that can be monitored with an installable on change trigger.
I am trying to add STATIC timestamp to my data whenever it is imported or pasted in the sheets.
I am using this formula now
(=ARRAYFORMULA( IFS(I1:I="","",L1:L="",NOW(),TRUE,L1:L)))
but, whenever I open the sheet again the time gets changed automatically to the current time as i am using the now() function. I tried on-Edit in the script, but it's only working when the data is manually entered.
Is there any other way I can use to static timestamp when data is being pasted or imported?
Instead of NOW() on the formula, do it via script using new Date().
The NOW() function updates the timestamp every time the spreadsheet is open or something changes in it, while the new Date() gives you a full (date and time) and static timestamp.
Also, as I've seen on the comments of your question, there really is no way to use onEdit() through automated scripts and macros.
Answer
You can use a custom function to return the actual date with the method new Date() and the Properties Service. Open Apps Script and paste the following function:
Code
function getTimestamp(reset) {
// update the timestamp
if (reset == 1) {
setTime()
}
// try-catch structure in order to set the time in the first execution
try {
var time = ScriptProperties.getProperty('time')
}
catch (err) {
setTime()
var time = ScriptProperties.getProperty('time')
}
return time
}
function setTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var time = new Date()
ScriptProperties.setProperty('time', time)
}
How it works
Now, you can use it in any cell like another Sheet function. Call the function with =getTimestamp(0). On the first execution, it tries to get the saved property time, but as the property does not exist it generates a timestamp and saves a new property in the project with the key time and the value of the timestamp.
In the following executions, the value obtained by the function when it is recalculated is the same, since the property is not overwritten unless the function is called with a 1 input: =getTimestamp(1). In this case, the timestamp is updated, but if it is not set back to =getTimestamp(0), every time the function is recalculated (which happens automatically every so often) the timestamp will change.
In conclusion, always use =getTimestamp(0). When you want to update the value, change it to =getTimestamp(1) and go back to the original formula.
update
I have updated the answer to explain how to update the timestamp when new values are added:
Use a cell as input to the function, e.g. =getTimeStamp(A1) 2.
Create an onEdit trigger
Check that the range of the e event belongs to new values.
Update the value of A1 to 1 and then to 0 if you have detected new values.
example:
function onEdit(e){
var range = e.range
var cell = SpreadsheetApp.getActiveSpreadsheet().getRange('A4')
if (range.columnStart > 1 && range.rowStart > 10){
cell.setValue(1)
SpreadsheetApp.flush()
cell.setValue(0)
}
}
If new values are added from column 1 and row 10, A1 is updated to 1 and then to 0, thus updating the value of the timeStamp function and saving it permanently until the trigger is executed again.
References:
Custom Functions in Google Sheets
Working with Dates and Times
Apps Script: Extending Google Sheets
Properties Service
Not sure have your question got a solution. I had the same struggle as yours over the year, especially with pasted data, and I found a solution that works for my case nicely (but not by formula, need to run in Apps Script).
Some background for my case:
I have multiple sheets in the spreadsheet to run and generate the
timestamp
I want to skip my first sheet without running to generate timestamp
in it
I want every edit, even if each value that I paste from Excel to
generate timestamp
I want the timestamp to be individual, each row have their own
timestamp precise to every second
I don't want a total refresh of the entire sheet timestamp when I am
editing any other row
I have a column that is a MUST FILL value to justify whether the
timestamp needs to be generated for that particular row
I want to specify my timestamp on a dedicated column only
function timestamp() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const totalSheet = ss.getSheets();
for (let a=1; a<totalSheet.length; a++) {
let sheet = ss.getSheets()[a];
let range = sheet.getDataRange();
let values = range.getValues();
function autoCount() {
let rowCount;
for (let i = 0; i < values.length; i++) {
rowCount = i
if (values[i][0] === '') {
break;
}
}
return rowCount
}
rowNum = autoCount()
for(let j=1; j<rowNum+1; j++){
if (sheet.getRange(j+1,7).getValue() === '') {
sheet.getRange(j+1,7).setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss");
}
}
}
}
Explanation
First, I made a const totalSheet with getSheets() and run it
with a for loop. That is to identify the total number of sheets
inside that spreadsheet. Take note, in here, I made let a=1;
supposed all JavaScript the same, starts with 0, value 1 is to
skip the first sheet and run on the second sheet onwards
then, you will notice a function let sheet = ss.getSheets()[a]
inside the loop. Take note, it is not supposed to use const if
your value inside the variable is constantly changing, so use
let instead will work fine.
then, you will see a function autoCount(). That is to make a for
loop to count the number of rows that have values edited in it. The
if (values[i][0] === '') is to navigate the script to search
through the entire sheet that has value, looking at the row i and
the column 0. Here, the 0 is indicating the first column of the
sheet, and the i is the row of the sheet. Yes, it works like a
json object with panda feeling.
then, you found the number of rows that are edited by running the
autoCount(). Give it a rowNum variable to contain the result.
then, pass that rowNum into a new for loop, and use if (sheeet.getRange(j+1,7).getValue() === '') to determine which row
has not been edited with timestamp. Take note, where the 7 here
indicating the 7th column of the sheet is the place that I want a
timestamp.
inside the for loop, is to setValue with date in a specified
format of ("yyyy-MM-dd hh:mm:ss"). You are free to edit into any
style you like
ohya, do remember to deploy to activate the trigger with event type
as On Change. That is not limiting to edit, but for all kinds of
changes including paste.
Here's a screenshot on how it would look like:
Lastly, please take note on some of my backgrounds before deciding to or not to have the solution to work for your case. Cheers, and happy coding~!
You cannot get a permanent timestamp with a spreadsheet formula, even with a named function or an Apps Script custom function, because formula results refreshed from time to time. When the formula gets recalculated, the original timestamp is lost.
The easiest way to insert the current date in a cell is to press Control + ; or ⌘;. See the keyboard shortcuts help page.
You can also use an onEdit(e) script to create permanent timestamps. Search this forum for [google-apps-script] timestamp to find many examples.
I have a google sheet with a variable amount of data, up to 1000 rows. I want to make a script that will create buttons in the spreadsheet for each row of data. Each button will trigger a script that will take values from a range in row the button is in and apply them to a Google Docs template.
The part I'm having problems with is creating the buttons with a script and passing arguments to the script, so that it will use the right row for each.
Is that even possible, or am I better off making a single button and modify the script to run through every row and create all the documents in one go?
You cannot create nuttons programmatically, but you can do so with checkboxes.
In combination with an onEdit trigger you can implement that every click of a checkbox will retrieve the row where this checkbox is located and will take this row as a function parameter.
Sample:
var checkBoxColumn=5;
function initialSetup(){
var sheet=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow=sheet.getLastRow()
var range=sheet.getRange(1, checkBoxColumn, lastRow);
// insert a checkbox in every row
var enforceCheckbox = SpreadsheetApp.newDataValidation();
enforceCheckbox.requireCheckbox();
enforceCheckbox.build();
range.setDataValidation(enforceCheckbox).setValue("FALSE")
}
function onEdit(e) {
if(e.range.getColumn()==checkBoxColumn&&e.range.getValue()==true){
var row=e.range.getRow();
copyData(row);
}
}
function copyData(row){
Logger.log("The checkbox was clicked in row "+row);
// do whatever you want with this row
}
I have this script to delete row that are checked with a checkbox.
function deleterows() {
var sheet = SpreadsheetApp.getActiveSheet();
sheet.showRows(1, sheet.getMaxRows());
var values = sheet.getRange('K1:K50').getValues();
values.forEach( function (r, i) {
Logger.log(r);
if (r[0])
sheet.deleteRows(i+1);
});
}
But it does not work properly - when I select multiple rows with the checkboxes - it deletes part of selected and part of the rows that follow selection
screenshot
I am new the JavaScripts so can someone help me please
By one hand, according to https://developers.google.com/apps-script/reference/spreadsheet/sheet#deleterowsrowposition-howmany, deleteRows require two parameters but your script is passing only one.
By the other hand, every time that a row is deleted the row number of the below rows changes, so it's better to iterate backwards. For more details see this other Q/A Deleting rows in google sheets using Google Apps Script
I have a Google spreadsheet where I would like to automatically update one field every time a new row is added by a Zapier automation.
Every time I test the script manually by adding a row or making edits on existing rows, the script fires as expected. However, when Zapier adds a new row to the spreadsheet absolutely nothing happens.
Any idea why and how I can remedy this problem?
This the script which I am trying to use:
function onEdit(e) {
var row = e.range.getRow();
SpreadsheetApp.getActiveSheet().
getRange(row, 10).setValue('My_New_Value');
}
I was able to replicate the behavior you are seeing in your script. I believe that when you add a row outside of the active data range, it does not trigger an edit event. In order to catch those events, you will also need to catch chagne events.
https://developers.google.com/apps-script/reference/script/spreadsheet-trigger-builder#onchange
function onOpen ()
{
var sheet = SpreadsheetApp.getActive();
ScriptApp.newTrigger("myFunctionx")
.forSpreadsheet(sheet)
.onChange()
.create();
}
Looking at the documentation for a change event, you can detect that a row was inserted, but the row number is not returned.
https://developers.google.com/apps-script/guides/triggers/events#change
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
}
You might be able to find the row that was inserted by looking at the last row in the active range.
function myFunctionx(e) {
Logger.log("Change3");
Logger.log(e.changeType);
Logger.log(SpreadsheetApp.getActiveRange().getLastRow());
}
It is possible for a change to affect more than one row if you copy and paste into multiple cells. You should loop through all of the rows in your range.
function onEdit(e) {
for(var i=e.range.getRow(); i<= e.range.getLastRow(); i++) {
SpreadsheetApp.getActiveSheet().
getRange(i, 10).setValue(e.range.getA1Notation());
}
}
onChange does trigger every time a new row is added(through sheets API).
function zapOnChange(e){
if(e.changeType !== 'INSERT_ROW') return;//exit if the type of change is not a row insertion
const insertedRow = SpreadsheetApp.getActive().getActiveRange();
//do something with inserted row
}
Important notes:
Sheets API runs asynchronously. onChange is triggered randomly and NOT in the order of insertion.
For eg, Two simultaneous inserts may occur at A2:F2 and A3:F3, But the insertRow at A2:F2 may trigger onChange after insertion of A3:F3. So, at the time zapOnChange() is run, the last row might already be A3:F3. So, you cannot use .getLastRow() to determine the inserted row, but use .getActiveRange()
Sometimes multiple rows are inserted: .getActiveRange().getNumRows() provides number of inserted rows.