How to run script on specific sheet and insert a formula - google-apps-script

I'm new and started learning to code this year.
I am trying to insert a formula into a specific sheet. The script is working and I am able to insert a new row and the formula, however the script runs even when I edit a different sheet. I only want it to run when cell A2 is edited on "Test" sheet.
Here is my code:
function onEdit(e) {
insertRow(e);
copyFormulas(e);
function insertRow(e) {
// get sheet
var sheet = SpreadsheetApp.getActive().getSheetByName("Test");
// Get the edited range
var editedRange = e.range;
if (editedRange.getA1Notation() === "A2") {
// check
sheet.insertRowBefore(2);
}
}
function copyFormulas(e) {
SpreadsheetApp
.getActive()
.getSheetByName('Test')
.getRange('C3')
.setFormula("=SUM(A3*B3)");
}
}
LINK to the sheet https://docs.google.com/spreadsheets/d/1AhwnzyKf6dSPHGKUPNuniZ0gtTihPDLaqAyEtAjNn8A/edit?usp=sharing

To accomplish this task, your code can be shortened to the following form
function onEdit(e) {
let range = e.range,
sheet = range.getSheet();
if (sheet.getName() == 'Test' && range.getA1Notation() == 'A2' ){
sheet.insertRowBefore(2);
sheet.getRange('C3').setFormula("=SUM(A3*B3)");
}
}
You can find a lot of useful information by reading the documentation on Event Objects Class Spreadsheet Class Range , or simply by searching for onEdit(e) on this site
I would add, sometimes instead of increasing the amount of formulas on the sheet (which causes the whole table to slow down), it is recommended to use an array formula that works for the whole column, and that you don't have to duplicate on every row.
In the case of your table, instead of adding a formula every time you insert a row with the following code sheet.getRange('C3').setFormula("=SUM(A3*B3)") - it is better to use one array formula in cell C1 ={"Col3";ArrayFormula(if(A2:A<>"",A2:A*B2:B,))}

Related

Google Apps script on cell background change recalculation [duplicate]

I know this question has been asked before but the answers given are not valid for my case because it's slightly different.
I've created a formula that looks for sheets with a pattern in the name and then uses it's content to generate the output. For example
function sampleFormula(searchTerm) {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheets = spreadsheet.getSheets()
.filter(function(sheet) {
// If sheet name starts with DATA_SHEET_...
return sheet.getSheetName().indexOf('DATA_SHEET_') === 0;
});
const result = [];
sheets.forEach(function(sheet) {
// We get all the rows in the sheet
const rows = sheet.getDataRange().getValues();
rows.forEach(function(row) => {
// If the row it's what we are looking for we pick the columns A and C
if (row[1] === searchTerm) {
result.push([ row[0], row[2] ])
}
});
});
// If we found values we return them, otherwise we return emptry string
return result.length ? result : '';
}
The thing is I need this formula to be re-calculated when a cell in a sheet with a name starting with DATA_SHEET_ changes.
I see most answers (and what I usually do) is to pass the range we want to watch as a parameter for the formula even if it's not used. But in this case it will not work because we don't know how many ranges are we watching and we don't even know the whole sheet name (it's injected by a web service using Google Spreadsheet API).
I was expecting Google Script to have something like range.watch(formula) or range.onChange(this) but I can't found anything like that.
I also tried to create a simple function that changes the value of cell B2 which every formula depends on but I need to restore it immediately so it's not considered a change (If I actually change it all formulas will break):
// This does NOT work
function forceUpdate() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
const range = sheet.getRange(1, 1);
const content = range.getValue();
range.setValue('POTATO');
range.setValue(content);
}
So I don't know what else can I do, I have like a hundred formulas on multiple sheets doing this and they are not updating when I modify the DATA_SHEET_... sheets.
To force that a custom function be recalculated we could use a "triggering argument" that it's only taks will be to trigger the custom function recalculation. This triggering argument could be a cell reference that will be updated by a simple edit trigger or we could use an edit installable trigger to update all the formulas.
Example of using a cell reference as triggering argument
=sampleFormula("searchTerm",Triggers!A1)
Example of using an edit installable trigger to update all the formulas
Let say that formulas has the following form and the cell that holds the formula is Test!A1 and Test!F5
=sampleFormula("searchTerm",0)
where 0 just will be ignored by sampleFormula but will make it to be recalculated.
Set a edit installable trigger to fire the following function
function forceRecalculation(){
updateFormula(['Test!A1','Test!F5']);
}
The function that will make the update could be something like the following:
function updateFormula(references){
var rL = SpreadsheetApp.getActive().getRangeList(references);
rL.getRanges().forEach(function(r){
var formula = r.getFormula();
var x = formula.match(/,(\d+)\)/)[1];
var y = parseInt(x)+1;
var newFormula = formula.replace(x,y.toString());
r.setFormula(newFormula);
});
}
As you can imagine the above example will be slower that using a cell reference as the triggering argument but in some scenarios could be convenient.

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.

Run a script every time there is a change in a cell

I want to run this sript every time there is a change in notes!C4 and automatically copy the value to notes!D4 cell
function Copy() {
var ss = SpreadsheetApp.getActive().getSheetByName("notes") ;
ss.getRange('notes!C4').copyTo(ss.getRange('notes!D4'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}
Update 1
If this it can't be done, I have in another spreadsheet a script that copies the link http://openinsider.com/screener?s= every time I run it to L2 cell of that sheet.
I'm taking that value with an =ImportRange to the actual spreadsheet. How can I copy that link to notes!D4 that's in another spreadsheet to the actual one?
function Refresh() {
var ss = SpreadsheetApp.getActive().getSheetByName("sheet1") ;
var cell = ss.getRange("L2");
cell.clearContent();
SpreadsheetApp.flush();
Utilities.sleep(5000); // You have 5 second to check that the cell has cleared
cell.setValue('http://openinsider.com/screener?s=');
}
Update 2
I've tried this, the first time the script was charging but it didn't make anything. I'm new to google scripts and I don't know how to make it working.
function onEdit(e) {
if (e.range.getA1Notation() === 'C4') {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("notes").getRange('D4').CopyPasteType.PASTE_VALUES;
}
}
As mentioned by #Cooper, you just need to use onEdit(e) Simple Trigger to run your script whenever there is a cell being modified.
I will just refer on your latest update, you can refer to this sample code:
function onEdit(e) {
var ss = e.source;
var cell = e.range;
if(cell.getA1Notation() === "C4" && ss.getActiveSheet().getName() == "notes"){
ss.getActiveSheet().getRange("D4").setValue(e.value);
}
}
What it does?
Get the spreadsheet object using Google Sheets events source parameter
Get the range object using Google Sheets events range parameter
Check if the modified cell is in sheet notes and in cell C4, I get the sheet object using Spreadsheet.getActiveSheet() method and get its sheet name using Sheet.getName(). This will make sure that your function will only run when Sheet notes!C4 was modified.
Set the value of the cell D4 using Range.setValue(value). To maximize the event object, I used value parameter in Google Sheets events.
Additional Tips:
Please be mindful of the methods available in each classes/objects that you are using. For example in this code:
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("notes").getRange('D4').CopyPasteType.PASTE_VALUES;
You get the range object of cell D4 using Sheet.getRange(a1Notation)
range object doesn't have CopyPasteType.PASTE_VALUES in its methods

Force Google Spreadsheet formula to recalculate

I know this question has been asked before but the answers given are not valid for my case because it's slightly different.
I've created a formula that looks for sheets with a pattern in the name and then uses it's content to generate the output. For example
function sampleFormula(searchTerm) {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheets = spreadsheet.getSheets()
.filter(function(sheet) {
// If sheet name starts with DATA_SHEET_...
return sheet.getSheetName().indexOf('DATA_SHEET_') === 0;
});
const result = [];
sheets.forEach(function(sheet) {
// We get all the rows in the sheet
const rows = sheet.getDataRange().getValues();
rows.forEach(function(row) => {
// If the row it's what we are looking for we pick the columns A and C
if (row[1] === searchTerm) {
result.push([ row[0], row[2] ])
}
});
});
// If we found values we return them, otherwise we return emptry string
return result.length ? result : '';
}
The thing is I need this formula to be re-calculated when a cell in a sheet with a name starting with DATA_SHEET_ changes.
I see most answers (and what I usually do) is to pass the range we want to watch as a parameter for the formula even if it's not used. But in this case it will not work because we don't know how many ranges are we watching and we don't even know the whole sheet name (it's injected by a web service using Google Spreadsheet API).
I was expecting Google Script to have something like range.watch(formula) or range.onChange(this) but I can't found anything like that.
I also tried to create a simple function that changes the value of cell B2 which every formula depends on but I need to restore it immediately so it's not considered a change (If I actually change it all formulas will break):
// This does NOT work
function forceUpdate() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
const range = sheet.getRange(1, 1);
const content = range.getValue();
range.setValue('POTATO');
range.setValue(content);
}
So I don't know what else can I do, I have like a hundred formulas on multiple sheets doing this and they are not updating when I modify the DATA_SHEET_... sheets.
To force that a custom function be recalculated we could use a "triggering argument" that it's only taks will be to trigger the custom function recalculation. This triggering argument could be a cell reference that will be updated by a simple edit trigger or we could use an edit installable trigger to update all the formulas.
Example of using a cell reference as triggering argument
=sampleFormula("searchTerm",Triggers!A1)
Example of using an edit installable trigger to update all the formulas
Let say that formulas has the following form and the cell that holds the formula is Test!A1 and Test!F5
=sampleFormula("searchTerm",0)
where 0 just will be ignored by sampleFormula but will make it to be recalculated.
Set a edit installable trigger to fire the following function
function forceRecalculation(){
updateFormula(['Test!A1','Test!F5']);
}
The function that will make the update could be something like the following:
function updateFormula(references){
var rL = SpreadsheetApp.getActive().getRangeList(references);
rL.getRanges().forEach(function(r){
var formula = r.getFormula();
var x = formula.match(/,(\d+)\)/)[1];
var y = parseInt(x)+1;
var newFormula = formula.replace(x,y.toString());
r.setFormula(newFormula);
});
}
As you can imagine the above example will be slower that using a cell reference as the triggering argument but in some scenarios could be convenient.

Triggering script by specific cell value

I am pretty new to google sheets script development and am wondering how to trigger a clearAll script with cell value i.e. A1=100.
My clearAll script works (see below), though I don't know what to add to it to trigger it using a specific cell value.
function clearAll() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formresponses1 = ss.getSheetByName("formresponses1");
formresponses1.clearContents();}
Thanks
If you are trying to make it so that whenever somebody puts in the value of "100", it clears the content of the entire sheet, then you can do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive() //gets the active spreadsheet
var sheet = SpreadsheetApp.getActiveSheet() //gets the active sheet
var cell = ss.getActiveRange() //gets the active cell
var cellContent = cell.getValue() //gets the value of the active cell
if(cellContent === 100) {
sheet.clearContents() //clears the values of the entire active sheet
}
}
If you want to make it so that whenever somebody edits the cell and makes its value "100", the code clears only that cell, then do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var cell = ss.getActiveRange()
var cellContent = cell.getValue()
if(cellContent === 100) {
cell.setValue("") //clears the value of the active cell
}
}
Of course, in the latter, due to the slight lag in Google scripts, if somebody rapidly puts in 100 in every cell they can, then some of the cells with a value of "100" will stay there, but if the person is putting the values in like a normal person rather than a spammer, then this code will work.
Also, if you are trying to make it so that if the value of a certain cell (ie: A1) is equal to "100," the script clears the entire sheet, do this:
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var sheet = SpreadsheetApp.getActiveSheet()
var cell = sheet.getRange('A1')
var cellContent = cell.getValue()
if(cellContent === 100) {
sheet.clearContents()
}
}
Hope I could help!
The onEdit trigger runs when any cell in the spreadsheet is edited.
Google Documentation - Spreadsheet On Edit
There is also a change trigger. It is an installable trigger, not a simple trigger.
Available types of triggers
Quote from documentation:
An installable change trigger runs when a user modifies the structure
of a spreadsheet itself — for example, by adding a new sheet or
removing a column.
I think the only thing that will work for you is the On Edit trigger. The trigger gets set from the Resources menu in the Apps Script Code editor.
I don't think you can restrict the code from running only to a certain cell, or a certain value within a cell. The code will run every time you edit ANY cell.
I think that the only other alternative would be to run a time based trigger, and have the script get the value of that cell, and check the value. The shortest time interval you can use is to run a script every minute. So if you edited the cell on second 1, it would take 59 more seconds before anything happened.
If you had some type of user interface, and the value in that cell was written to when the user entered a value in an input field, you could detect that change immediately, and make something happen.