we have the following Google Sheet Script, which is working fine but we want to auto run it when only sub sheet "test" range A4 edit or change,
We make any change or edit on Sub Sheet 'test' Range A, it will run
function CopyPasteVal() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('test'), true);
spreadsheet.getRange('B:B').activate();
spreadsheet.getRange('C:C').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
spreadsheet.getActiveRangeList().setNumberFormat('0');
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('test'), true);
spreadsheet.getRange('A4').activate();
}
but if we make any change in other sub sheet, above code will not run.
Explanation:
Please note the following two things:
You don't need to activate ranges or sheets unless you explicitly want to see the changes in your sheet as the script is running.
You need an onEdit(e) trigger that will take full advantage of the event object. Then you can make sure that a block of code is only executed when the active sheet (the one you edit) is the test sheet and the edited cell is the cell A4.
Solution:
function onEdit(e) {
const range = e.range;
const spreadsheet = e.source;
const as = spreadsheet.getActiveSheet();
if(as.getName()=='test' && range.getA1Notation() == "A4"){
const destRng = as.getRange('B:B');
as.getRange('C:C').copyTo(destRng, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
destRng.setNumberFormat('0');
}
}
You need to put the logic in a simple trigger function, in this case, onEdit(e), and check if the active sheet is "test" and the edited range, e.range is exactly cell A4:
function onEdit(e) {
var range = e.range;
var spreadsheet = SpreadsheetApp.getActive();
if (spreadsheet.getActiveSheet().getSheetName() == "test" && range.getA1Notation() == "A4") {
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('test'), true);
spreadsheet.getRange('B:B').activate();
spreadsheet.getRange('C:C').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
spreadsheet.getActiveRangeList().setNumberFormat('0');
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('test'), true);
spreadsheet.getRange('A4').activate();
}
}
References:
Simple Triggers
Related
I am creating a macro for my repetitive work.
What I want to do:
Copy a current cell in Sheet1
Paste it to Sheet2
There will be a =filter formula running at the bottom, and I need to navigate to the bottom and copy the range of outputs
Have the cell return back to its original A1 cell position in Sheet2
Paste the outputs to Sheet3, and move down one row
Loop it until it meet an empty cell in the row of Sheet1
Challenges I face:
In Sheet1: The macro I created only refer to the cell I first run the macro with (even after I start from a different cell, the macro still copying the same initial cell)
In Sheet2: The outputs will possibly be a row or multiple rows of output, so it seems like Ctrl+A during the macro may not quite do the work.
For Loop: The macro only me to run once, but I will need it run repetitively until it meets an empty cell in Sheet1.
Challenge 1 and 2 are my main challenges, I can manually use hotkeys to run the macro if I can't get the macro to loop, but will be definitely grateful if someone can teach me how to loop it.
function CleanUp6() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Sheet2'), true);
spreadsheet.getRange('\'Sheet1\'!C2').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
var currentCell = spreadsheet.getCurrentCell();
spreadsheet.getActiveRange().getDataRegion().activate();
currentCell.activateAsCurrentCell();
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.UP).activate();
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.UP).activate();
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.UP).activate();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Sheet3'), true);
spreadsheet.getCurrentCell().offset(1, 0).activate();
spreadsheet.getRange('\'Sheet2\'!A8:D8').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}
Using macro recorder to try to develop a script is not a good idea. It is recording mouse and keyboard actions, not code logic. I think what you want is something like this. Can't be sure but try it on a copy of your spreadsheet. Add a menu option with the onOpen() trigger.
function onOpen() {
var menu = SpreadsheetApp.getUi().createMenu("File Picker");
menu.addItem("Test","test");
menu.addToUi();
}
function test() {
try {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
if( sh.getName() !== "Sheet1") {
SpreadsheetApp.getUi().alert("Change to Sheet1");
return;
}
if( sh.getActiveCell().getA1Notation() !== "A1" ) {
SpreadsheetApp.getUi().alert("Change to cell A1");
return;
}
// get the values in A:A
var values = sh.getRange(1,1,sh.getLastRow(),1).getValues();
sh = ss.getSheetByName("Sheet2");
// copy values from Sheet1 to Sheet2
// assumes the formulas are outside of the range values
sh.getRange(1,1,values.length,1).setValues(values);
// get the last row
values = sh.getRange(sh.getLastRow(),1,1,sh.getLastColumn()).getValues();
sh = ss.getSheetByName("Sheet3");
// get the row following the last row .getLastRow()+1
sh.getRange(sh.getLastRow()+1,1,values.length,values[0].length).setValues(values);
}
catch(err) {
console.log(err);
}
}
I want to create a script that will display the cell address of the active cell in A1, however, this is proving difficult because I can't even get the onSelectionChange example on the Google Apps Script guide to work. Whenever I run it, the error I get is TypeError: Cannot read property 'range' of undefined.
This is the code:
/**
* The event handler triggered when the selection changes in the spreadsheet.
* #param {Event} e The onSelectionChange event.
*/
function onSelectionChange(e) {
// Set background to red if a single empty cell is selected.
var range = e.range;
if(range.getNumRows() === 1
&& range.getNumColumns() === 1
&& range.getCell(1, 1).getValue() === "") {
range.setBackground("red");
}
}
About your error message of TypeError: Cannot read property 'range' of undefined, I thought that you might have directly run the function of onSelectionChange with the script editor. If it's so, such error occurs because the event object e is not given.
onSelectionChange is used as the simple trigger. In this case, when you want to retrieve the cell address of the active cell with onSelectionChange trigger, please select the cell on Google Spreadsheet. By this, the script is run by the onSelectionChange trigger.
Sample script:
For example, when the onSelectionChange trigger is run by selecting a cell, the sample script that the cell address is put to the cell as the A1Notation is as follows. When you use this script, please copy and paste the following script to the script editor of Spreadsheet and save it. And, please select a cell on the active Spreadsheet.
function onSelectionChange(e) {
var range = e.range;
range.setValue(range.getA1Notation());
}
Result:
When above script is used, the following result is obtained. You can see that in order to run the script, the cell is selected.
Note:
Even when you selected a cell, when the sample script is not run, please close the Spreadsheet and open the Spreadsheet again. In my environment, I confirmed that by this, the onSelectionChange trigger worked.
Reference:
onSelectionChange(e)
Added 1:
About the following your replying,
Thanks. This works and so did the red cell Google example. Although I did have to close the workbook and reopen it to get it to work. How can I set the range to a fixed cell so the active cell address only appears there?
When you want to run the script for only the specific range, how about the following sample script? From your replying, I used your script for this.
Sample script:
function onSelectionChange(e) {
var ranges = ["A2:B5", "D2:E5"]; // Please set the range you want to run the script.
var range = e.range;
var sheet = range.getSheet();
var check = ranges.some(r => {
var rng = sheet.getRange(r);
var rowStart = rng.getRow();
var rowEnd = rowStart + rng.getNumRows();
var colStart = rng.getColumn();
var colEnd = colStart + rng.getNumColumns();
return (range.rowStart >= rowStart && range.rowEnd < rowEnd && range.columnStart >= colStart && range.columnEnd < colEnd);
});
if (check) {
range.setBackground("red");
}
}
In this script, only when the cell in the range of var ranges = ["A2:B5", "D2:E5"] is selected, range.setBackground("red") is run. So please modify ["A2:B5", "D2:E5"] for your actual situation.
If you want to also check the sheet name, please modify above script as follows. In this case, when the cell in the ranges ["A2:B5", "D2:E5"] of the sheet ["Sheet2", "Sheet3"] is selected, range.setBackground("red") is run.
function onSelectionChange(e) {
var sheetNames = ["Sheet2", "Sheet3"]; // Please set the sheet names.
var ranges = ["A2:B5", "D2:E5"]; // Please set the range you want to run the script.
var range = e.range;
var sheet = range.getSheet();
if (!sheetNames.includes(sheet.getSheetName())) return;
var check = ranges.some(r => {
var rng = sheet.getRange(r);
var rowStart = rng.getRow();
var rowEnd = rowStart + rng.getNumRows();
var colStart = rng.getColumn();
var colEnd = colStart + rng.getNumColumns();
return (range.rowStart >= rowStart && range.rowEnd < rowEnd && range.columnStart >= colStart && range.columnEnd < colEnd);
});
if (check) {
range.setBackground("red");
}
}
Added 2:
About the following your replying,
No, sorry. I meant how can I make the active cell address appear in A1. If I click on cell G5, for example, 'G5' appears in A1.
In that case, how about the following sample script?
Sample script:
function onSelectionChange(e) {
var range = e.range;
range.getSheet().getRange("A1").setValue(range.getA1Notation());
}
Using the code below I am able to generate a hyperlink to each page within my googlesheet, with the name of that sheet:
/**
* Gets the Sheet Name of a selected Sheet.
*
* #param {number} option 0 - Current Sheet, 1 All Sheets, 2 Spreadsheet filename
* #return The input multiplied by 2.
* #customfunction
*/
function SHEETNAME(option) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet()
var thisSheet = sheet.getName();
var thisgid = sheet.getSheetId();
//Current option Sheet Name
if(option === 0){
return thisSheet;
//All Sheet Names in Spreadsheet
}else if(option === 1){
var sheet1 = [];
ss.getSheets().forEach(function(val){
sheet1.push(`=HYPERLINK("#gid=${val.getSheetId()}","${val.getName()}")`)
});
return sheet1;
//The Spreadsheet File Name
}else if(option === 2){
return ss.getName();
//Error
}else{
return "#N/A";
};
};
function GETLINK(option) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(option);
if (sheet != null) {
return(sheet.getId());
};
};
This returns a column of appropriate functions in each cells but does not evaluate the function. If I copy the function into another cell it does evaluate?!
=HYPERLINK("#gid=XXXXXXXXXXXXX","SHEET1")
I've tried also tried adding HTML but this also becomes a string and is not evaluated
sheet1.push(`${val.getName()})
When the custom formula of =SHEETNAME(0) is put to a cell, you want to put the sheet name of the active sheet on the Spreadsheet.
When the custom formula of =SHEETNAME(1) is put to a cell, you want to put the formulas of =HYPERLINK("#gid=XXXXXXXXXXXXX","SHEET1") for all sheets.
When the custom formula of =SHEETNAME(2) is put to a cell, you want to put the Spreadsheet name of the active Spreadsheet.
If my understanding is correct, how about this answer?
Issue and workaround:
Unfortunately, in the current specification of Google side, the formula cannot be put to a cell by the custom formula. When the formula is put to a cell by the custom formula, the formula is put as the string value. I think that this is the reason of your issue.
As a workaround, I would like to propose to put the formula of =HYPERLINK("#gid=XXXXXXXXXXXXX","SHEET1") using the OnEdit event trigger when the custom formula of =SHEETNAME(#) is put to a cell. I think that by this workaround, your goal can be achieved.
When your script is modified, please modify as follows.
Modified script:
In this case, the simple trigger can be used. So please copy and paste the following script to the script editor, and save the script. In order to use this script, as a test, please put =SHEETNAME(1) to a cell.
function onEdit(e) {
const spreadsheet = e.source;
const range = e.range;
const sheet = range.getSheet();
const formula = range.getFormula();
const f = formula.match(/\=SHEETNAME\((\d+)\)/i);
if (f && f.length > 0) {
if (f[1] == "0") {
range.setValue(sheet.getSheetName());
} else if (f[1] == "1") {
var formulas = spreadsheet.getSheets().map(val => [`=HYPERLINK("#gid=${val.getSheetId()}","${val.getName()}")`]);
range.offset(0, 0, formulas.length, 1).setFormulas(formulas);
} else if (f[1] == "2") {
range.setValue(spreadsheet.getName());
}
}
}
In this case, there is the function of SHEETNAME() in your script editor. So when =SHEETNAME(1) is put to a cell, at first, the function of SHEETNAME() is run, and then, the function of onEdit is automatically run by the OnEdit event trigger. If you don't want to show the values from SHEETNAME(), please replace the function of SHEETNAME() as follows.
const SHEETNAME = () => "";
Note:
In this case, when you directly run the function of onEdit at the script editor, an error occurs. Please be careful this.
This script is run under V8.
References:
Simple Triggers
Event Objects
setFormulas()
I am trying to run a script on my spreadsheet that hides columns on one sheet based on whether a checkbox in another sheet is ticked.
I have created the below code but this doesn't seem to work:
function onEdit(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = SpreadsheetApp.getActive().getSheetByName('BALANCE SHEET');
var sheet2 = SpreadsheetApp.getActive().getSheetByName('OVERVIEW')
var cell = sheet2.getRange(7, 5);
if(cell == "TRUE"){
sheet1.hideColumns(21,2)};
if(cell == "FALSE"){
sheet1.showColumns(21,2)};
}
The tickbox is on the sheet OVERVIEW in cell E7 and I want this to hide columns V & W on the BALANCE SHEET.
When the tickbox is TRUE, I want the columns to be displayed and when the tickbox is FALSE the columns will be hidden.
Can anyone help on this please?
This works:
Note: you can't run this from the script editor as it requires having the onedit trigger object. I have my checkbox at 'OVERVIEW!E7';
function onEdit(e){
//e.source.toast('Test');
var sh=e.range.getSheet();
if(sh.getName()!='OVERVIEW')return;
if(e.range.columnStart==5 && e.range.rowStart==7) {
//e.source.toast('Test1');
var sheet1=e.source.getSheetByName('BALANCE SHEET');
if(e.value=="TRUE"){sheet1.hideColumns(21,2)};
if(e.value=="FALSE"){sheet1.showColumns(21,2)};
}
}
Please see example picture below.
I would like to use Google Sheets script editor to add a Filter view on cell B3, that filters on a custom formula =(B4=$B$1).
The plan is to add a onEdit function that checks whether the value in cell B1 has changed, and if so reset the filter view in cell B3.
This is breaking my brain! I can't seem to find a way to auto update/refresh the filter once my value in B1 has changed.
My code currently looks like this, but this doesn't re-add the custom formula. It just keeps showing me all the non-empty rows.
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheetname = "Lactation";
var sheet = ss.getSheetByName(sheetname);
var erow = e.range.getRow();
//Logger.log("DEBUG: row = "+erow);
var ecolumn = e.range.getColumn();
//Logger.log("DEBUG: column = "+ecolumn);
if (erow == 1 & ecolumn == 2 ){
// there is a match so the cell is B2
//Logger.log("DEBUG: the cell is B2");
updatefilter();
}
else
{
// there is no match so the cell is NOT I3
//Logger.log("DEBUG: the cell is not I3");
}
}
function updatefilter() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('B3').activate();
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues(['', 'No'])
.build();
spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(2, criteria);
};
You want to modify the basic filter of the cells "B3:B" when the cell "B1" on the sheet Lactation is edited.
In this case, you want to use the OnEdit event trigger.
You want to use =(B4=$B$1), which is the custom formula, as the filter condition.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
You can take advantage of the event object as e of onEdit(e).
In order to use the custom function for the basic filter, you can use whenFormulaSatisfied.
Modified script:
In order to run the script, please edit the cell B1 on the sheet Lactation.
function onEdit(e){
var sheetName = "Lactation";
var editCell = "B1";
var range = e.range;
var sheet = range.getSheet();
if (range.getA1Notation() == editCell && sheet.getSheetName() == sheetName) {
updatefilter(sheet);
} else {
// do something
}
}
function updatefilter(sheet) {
sheet.getRange('B3').activate();
var criteria = SpreadsheetApp.newFilterCriteria().whenFormulaSatisfied("=(B4=$B$1)").build();
var filter = sheet.getFilter();
if (!filter) {
filter = sheet.getRange("B3:B").createFilter();
}
filter.setColumnFilterCriteria(2, criteria);
};
In this script, when the cell "B1" on the sheet of Lactation is edited, the function updatefilter is run by the OnEdit event trigger. And when the basic filter is existing, it is updated. When no filter is existing, the filter is set as new filter. The range of the filter is "B3:B". The custom function of =(B4=$B$1) is used as the criteria.
Note:
In above script, the simple trigger can be used. But if you use the methods for requiring to authorize, when an error occurs, please try to use Installable Triggers.
References:
Simple Triggers
Installable Triggers
Event Objects
whenFormulaSatisfied(formula)
If I misunderstood your question and this was not the direction you want, I apologize.