Apps Script: How to capture oldValue of a cell after a change made by sheets api? - google-apps-script

I am running a python script that updates my google sheet (named 'Notifications') through the sheets api every 5 minutes. The python script first clears and resizes the sheet to only two rows and then adds the new data on top and the sheet's size gets automatically expanded. I've done that not to have empty rows if my new data is smaller in size than the old one.
So the sheet is updated every 5 minutes but if nothing new happened in my data source, the new values are going to be the same as the old. In particular, the value in 'A2' is going to be the same as before the update. If something new has happened, the value in 'A2' is going to be different. I want to track that and get a pop-up message that notifies me that something new has happened after the update.
So as far as I understand I have to use onChange because onEdit only works if the changes are made by a human and cannot track changes made by the api. I'm uncertain how to do that though.
With onEdit I've achieved it and get a pop-up if I modify the content of 'A2' manually but nothing happens when python updates my sheet.
Here's the code with onEdit:
function onEdit(e) {
if (e.range.getA1Notation() === 'A2') {
let oldValue = e.oldValue
let newValue = e.newValue
if (oldValue != newValue) {
Browser.msgBox('The value in A2 changed from ' + oldValue + ' to ' + newValue);
}
}
}
So the end goal is to get a pop-up not every time python updates my sheet, but only if there's a change in the value of 'A2' after the update.
How can I do this?

The only way to get a pop-up when a a change is made through an API is by using polling function from client-side code. One way to do this is by using a sidebar to hold the client-side code.
You should store somewhere the old values then compare with the current values.
Related
How do I make a Sidebar display values from cells?
How to get all old values for a mulitple cell range with onEdit trigger
onEdit(e) not generating trigger event when cell value changes due to inbuilt function

You can use onChange trigger with a "ValueInputOption"="USER_ENTERED" to trigger a function when a script edits a sheet. But you can't show anything in the UI from the triggered context so getting a pop up will be impossible.

Related

Trigger bug when copying rows

I have a function that copies rows from one sheet to another, which works when I run it manually:
function updateDataRange() {
var formSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1");
var lastFormRow = formSheet.getLastRow();
var dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DataRange");
dataSheet.clear();
for(var rowCounter = 1; rowCounter <= lastFormRow; rowCounter++) {
var sourceRange = formSheet.getRange('B'+rowCounter+':H'+rowCounter);
var insertRow = dataSheet.getLastRow() + 1;
var targetRange = dataSheet.getRange('A'+insertRow);
sourceRange.copyTo(targetRange);
}
}
However, when I run this via a trigger (e.g. function onEdit(e) { updateDateRange(); } ), there are gaps in the rows and some of the values will be left out. In this case, I know there is a "fix" by using rowCounter instead of insertRow to write the files instead of using getLastRow(), but one can easily see how this is a problem in another scenario.
So I guess my question is simple: Why isn't this working correctly when using a trigger?
Edit: To clarify, I have a 3rd sheet with some conditions/cells (i.e. conditions that influence which rows are to be copied) and changing them (like changing a different date) trigger the function.
Explanation:
You are using an onEdit trigger to capture sheet changes. But onEdit triggers work only when the user changes the value of a cell. Referencing the official documentation:
The onEdit(e) trigger runs automatically when a user changes the value
of any cell in a spreadsheet.
In your case, I think you are trying to execute this code when the Form Responses sheet is filled in with data by the form.
There are two kind of trigger functions that work with form submissions and they are both installable.
One is a google sheet event based trigger and the other one a form event based trigger.
Your goal is to execute some code from the google sheets side, so it makes sense to use the first one.
Modifications:
Change the name of the function from onEdit to a different name of your choice e.g. myFormSubmitTrigger.
Since the trigger is installable you need to "install" a onFormSubmit trigger for the myFormSubmitTrigger function. Here you can find some simple instructions on how to do that.
If your question is why doesn't your function work when the form makes changes to sheet Form Responses 1 then Marios has answered you question accept it and move on. If your trying to run your function when a user is making edits to sheet Form Responses 1 using a simple trigger then a possible explanation for not getting all of the rows completely copied is that simple trigger must complete in 30 seconds. If you continue to accept more data in Form 1 Responses then soon or later you will have problems but with this function it will be a lot later because it will run much faster than your code:
function updateDataRange() {
const sss=SpreadsheetApp.openById('ssid');
const ssh=e.source.getSheetByName('Form Responses 1');
const sr=2;
const svs=ssh.getRange(sr,2,sh.getLastRow()-ssr+1,7).getValues();
const dsh=e.source.getSheetByName('DataRange');
dsh.clear();
dsh.getRange(1,1,svs.length,svs[0].length).setValues(svs);
}
However, I would recommend that you never edit a form responses sheet. I would consider using the onFormSubmit trigger to capture all of the data to a second sheet that is available to edit. And it will have additional data automatically appended to it via dsh.appendRow(e.values). And so now you would no longer require an onEdit trigger because your data sheet is kept upto data with an onFormSubmit trigger and you may feel free to edit the datasheet any time you wish.
If neither of these questions suffices then I would recommend that you be more clear about what you are asking.

Call custom function in google sheets only once and save value to cell

I have written a custom google apps script function that is being called by a google sheet. In this function an API is called and the result of this API is being returned so that it gets displayed in the calling cell of the google sheet.
My problem now is that the sheet calls this function everytime I open the document and there are over 80.000 cells with this function.
Is it possible to save the returned value to the cell and don't call the custom function again when an value has been returned? I want the function being called only once per cell, even if I close the document and reopen it. The returned value should be saved until something else is being written into to cell. That would make my sheets document much more usable than the current state.
From the question
Is it possible to save the returned value to the cell and don't call the custom function again when an value has been returned? I want the function being called only once per cell, even if I close the document and reopen it. The returned value should be saved until something else is being written into to cell. That would make my sheets document much more usable than the current state.
By solely using the custom function it's not possible. There isn't a straight forward solution is to achieve this, in order to keep things simple you should look for another way to implement what is being done by the custom funtion.
One option is to use a trigger (including a custom menu / a function assined to a drawing) to check if the cell that should have the value returned by the custom function is empty and in such case fill it with the corresponding value. The best trigger to use will depend on your spreadsheet workflow.
As specified by Ruben this is not possible, with a custom function.
In my particular case I have resorted to using an Apps Script function that is triggered by an edit event of the spreadsheet and verifies if the event is in the column where the function that I want to execute only once should be, later replacement its content with the result of calling the API.
function freezeValue(e) {
var rangeEvent = e.range;
var col = rangeEvent.getColumnIndex();
if (col === 2) { #Verify column of event
var value = rangeEvent.getValue();
/*Call your api*/
result_api = CALL_API(value)
rangeEvent.setValue(result_api);
}
}
Keep in mind that this implementation only works when each of the cells is edited one by one. To do the same with a row or a complete array of elements, you must go through each of the cells and follow the same procedure.

Is it possible to set Notes with automatically inserted values using google sheets?

I was trying to figure out a way to insert Notes automatically, when i enter something in a specific cell. This thread was suggested to me and tried it out: https://support.google.com/docs/thread/13317657?hl=en. I changed the function mentioned a littlebit, to fit my project:
function onEdit(e) {
if(e.source.getActiveSheet().getName() === 'Testsheet') {
if(e.range.columnStart === 8 && (e.range.rowStart > 12 && e.range.rowStart < 21)) {
return e.range.offset(0, -6).setNote(e.value);
}
}
}
This way when I enter values in the range H13:H20, the same values get inserted as Notes on the corresponding (0, -6) offset cells. Which is a step in the right direction, but not yet what I'm truly trying to achieve. The problem is, this only works, when the values are entered manually. But for my project, I need the notes also to be set, when values are filled in automatically, using a function that fills in certain information in a row, depending on the selected dropdown choice.
In my sheet for example:
=if(B17="Test1",Testsheet!C10:Testsheet!H10)
When the Information is filled in that way, it doesn't give me the Note in the offset cell. Is there a way, so that it does?
Here is a link to my Testsheet, where you can see what I'm trying to do : https://docs.google.com/spreadsheets/d/16c2hVDM_FTnHWerKjnOauCopwVZSnKCwAMVi6-Z8vBo/edit?usp=sharing
Thanks for any tips and help in advance!
As you can see in the documentation:
Script executions and API requests do not cause triggers to run. For
example, calling Range.setValue() to edit a cell does not cause the
spreadsheet's onEdit trigger to run.
The only option would be to directly run the notes function with the values as parameter instead of inserting the "row 16" values, as updating the sheet by a script or formula won't trigger the function onEdit().

Google Sheets: Cache IMPORTXML data

So I am trying to just create a simple Movie list for myself and want to keep the IMDB/Meta/RT scores in a cell.
I have it working fine with something like this:
=IF(ISBLANK(A2),,IFERROR(importxml(("http://www.omdbapi.com/?apikey=**MYKEY**&t="&A2&"&r=xml&tomatoes=true&y=2018"),"root/movie/#imdbRating")/10))
This will return a value that I can keep in the cell. The PROBLEM is that it doesn't always refresh. Either google sheets is bugged, importxml, or the omdbapi. I have read others having the same issue.
A typical row looks like this:
[Black Panther] [February 16, 2018] [86%] [74%] [97%] [88%]
Since my data does not change much I wanted to somehow cache it. So if the importxml fails it won't blank out the cell, it will just keep whatever the last value was. Maybe I can do this by referring to another tab's cell? I did try that, but as soon as the other tab cell gets blanked out(because the importxml fails) so does the main tab's cell.
Thoughts?
I believe this requires an apps script that will add each change you make to row 1, to a corresponding target column 2 - if not empty/NA:
function onEdit(e) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
if(sh.getActiveCell().getColumn() == 1) {
if(e.value && e.value != '#N/A')
sh.getRange(e.range.rowStart, 2).setValue(e.value);
}
}
Goto Tools, Script Editor, from the menu and add the script. Make sure to run it once in the debugger and grant the required permissions.
Also, take note of this references:
Refresh data retrieved by a custom function in Google Sheet
How to debug Google Apps Script (aka where does Logger.log log to?)
https://webapps.stackexchange.com/questions/42204/how-do-i-automatically-update-a-cell-in-google-spreadsheets-to-the-most-recently

How do I get the Active Sheet from an On Change event?

Did a search on this site for "INSERT_ROW getActiveSheet" and found one hit: Google Script: How can I run a script only when someone adds new rows. The accepted answer shows this code:
function myFunction(e){
Logger.log(e.changeType);
if(e.changeType=='INSERT_ROW'){
// do Something
Browser.msgBox('New row(s) added');
}
}
which is fine. The problem I am having is in the "// do something" part of the code, what I want to do is get the Active Sheet and ultimately the Active Range so that I can add formulas to the new rows programmatically when a user inserts new rows. I have tried both:
e.source.getActiveSheet().getName()
and
SpreadsheetApp.getActiveSheet().getName()
to attempt to get the name of the Active Sheet, but it keeps returning the first sheet in the spreadsheet and the range A1.
When I run this code:
Browser.msgBox(Utilities.jsonStringify(e));
it does not return the active sheet, only the authMode, changeType and user properties, as it shows it should in the documentation.
There must be a way, however, to find out what has been changed, otherwise what is the point of an onChange event?
I believe the problem was this bug, which is now fixed.