How do I get the Active Sheet from an On Change event? - google-apps-script

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.

Related

How to force the user to always keep only one cell selected?

Does anyone have any suggestions on how to force the user to always keep only one cell selected in Google Sheets?
I tried to create a script with the onSelectionChange event, but in all tests when I select a range I can't force the selection to be active only in the first upper left cell of the range.
My intention is to prevent the user from selecting a range and accidentally deleting or changing cells with formulas. I will make the spreadsheet available to someone else, and not share, so the native range protection feature is useless, as at most I can put an alert message in the range, but it does not prevent the user from deleting or changing the protected range.
I've already got efficient ways to protect cells with formulas since the change or the delete started from a single cell selection, now I need to prevent the selection scenario of a range.
I got it:
function onSelectionChange(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName();
if (sheet == "MySheet") {
SpreadsheetApp.getActiveSheet().getActiveCell().activate()
}
}
I just find it bad that Google Sheets fluctuates a lot in trigger response time. Sometimes it runs immediately, sometimes it takes one to three seconds to run.

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

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.

In google sheets - how do I apply an onEdit trigger to run on just one cell on one sheet?

Thanks in advance for your help ...
I need to run a script upon editing just a single cell in a specific sheet.
A new row is first appended to the bottom of the dataRange.
The user first selects a date from a datePicker in Col A and then selects an entry from a list (using Data Validation) in Col B.
The idea is to use the textFinder to find the first previous row that matches the selected list item in Col B so that other cells on the new row can be populated from the found matched row.
All is working well except for the trigger.
Any ideas on how to trigger the script would be gratefully received!
Answer:
You can use an onEdit(e) trigger containing a conditional which only executes if a certain cell has been edited.
Code:
You can run whatever script you like on the edit of a specific cell by first checking the range of the edit using the event object:
function onEdit(e) {
if (e.range == 'A1') {
// put the code you want to run here
}
// you can put additional else if statements here if you want other code to
// execute on other cells being edited
else {
return;
}
}
Make sure to run the script manually one time first so that you can authenticate!
References:
Simple Triggers
Event Objects

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 to fix this error in programmatically (with trigger) active() the last occupied row in a Google sheet?

Supose you have a sheet with several hundred rows long. You could to this to go to the last row and start your work:
1) after opening the spreadsheet, do Ctrl End; Ctrl leftArrow; Ctrl
upArrow
this will take you to the last occupied row cell in column A 2) before closing the spreadsheet, select the last occupied cell in
column A and assign it range name endRow then when you open the
spreadsheet do Edit > Named ranges then click on endRow this will take
you to the last occupied row cell in column A;
I just find a way to programmatically implement this, it's really simple, but worked only in the oldspreadsheet. I just put this line inside onOpen() trigger and just works fine: activeSheet.getRange(activeSheet.getLastRow(), 1).activate();.
But it didn't work in the new Google Sheets (a new one with some thousands of rows).
So, I tried the Zig advice, write this function:
function goToLastRow() {
var s = SpreadsheetApp.getActiveSpreadsheet();
var range = s.getRange(s.getLastRow()+1, 1);
range.activate();
}
And set the trigger manually (Script Editor>>Resources>>Triggers from this project>>Ad new trigger). But it still didn't work.
Any suggestion?
I found this way to accomplish what you need:
function onOpen() {
var sheet = SpreadsheetApp.getActiveSheet();
sheet.hideRows(1,sheet.getLastRow());
}
This hides all rows with data from the active sheet, places the cursor on the next (empty) row, so you can start typing away.
when done with the row, simply click the little triangle to the left of the hidden rows, and they will reappear if you need so.
Might cause problems if more than one person enters new rows at the same time.
★ Here is another cool way to accomplish this without any scripting, plus works even with simultaneous users (the script version above could hide a row being entered if another person opens the spreadsheet with edit permissions.)
This 2nd no-script solution will only work if your data has a column with a fixed set of values (¹), for example a column with only the possible values 'pending', 'doing' and 'done'. So find such column, which I assume its on column 'A' in this example.
➮ Lets use "Filter Views" in the "Data" menu (only in the new sheets versions.)This feature allows for personalized filters that only the user applying them sees. Create a filtered view called "Enter new rows mode" with no values checked for column 'A' (click filter:"clear" when you drop-down the column filter menu).
➮ Now, when you enter the spreadsheet, the filtered view is applied automatically and all rows with data will go away.Start typing away. If other users start entering new rows, your row only goes away for them, not for you until you refresh the browser window.
➮ If other users remove the filtered view, its removed only for them, not for you.
You can also do something similar with the "old" filters but it has the same dissadvantage as the script solution regarding multiple users.
*¹ A fixed set of values is needed because the spreadsheet filters automatically add new items as checked in the view. You cant use a date column because you will always get new future values.To configure the filter you need to already have at least one row per possible value, so that you can uncheck them in the column filter.If new values are later entered, you need to edit the view to uncheck it.
I just find the way, it's really very simple, just put this line inside onOpen() trigger and just works fine: activeSheet.getRange(activeSheet.getLastRow(), 1).activate();.
But it didn't work in the new Google Sheets.
So, I tried the Zig advice, write this function:
function goToLastRow() {
var s = SpreadsheetApp.getActiveSpreadsheet();
var range = s.getRange(s.getLastRow()+1, 1);
range.activate();
}
And set the trigger manually (Script Editor>>Resources>>Triggers from this project>>Ad new trigger).
Any suggestion?
Change getActiveSpreadsheet to getActiveSheet. The spreadsheet is all the file, a sheet is just a sheet :P. But you will find that you cant go to any part of a spreadsheet in an onOpen trigger.
The best way is to create a menu for this.
At the end the code goes like this:
function onOpen() //the same to create a onOpen trigger
{
SpreadsheetApp.getUi().createMenu("Go to").addItem("Last Row", "goToLastRow").addToUi();
}
function goToLastRow() {
var s = SpreadsheetApp.getActiveSheet();
Logger.log(s.getLastRow());
var range = s.getRange(s.getLastRow()+1, 1);
range.activate();
}