How to make the cell reference in Google App Script dynamic? - google-apps-script

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);
}
}

Related

Apply two scripts to a spreadsheet that has multiple sheets

I was able to find script that work for each sheet in a separate file, but can I have 2 scripts applied to a single file, but 2 sheets respectively? Essentially what I am after is this:
I am a pilot and I keep track of my logbook and flight times in a Google Sheets file. I have used this means for years but recently tried to apply some scripts to get to the first empty cell in column A as that is where I start with the date for each entry. The main reason for this is my sheet is now in excess of 3000 rows of entries. The first sheet in my file where I record my flights is titled "LOGBOOK". I have found a script to apply onOpen that will place my cursor to the first empty row of column A each time I open. Below is the script that I use. It works but takes approx 10 seconds to complete. If there is a faster means, I am open to suggestions. I only want to know the first empty cell in column A. This is what I use currently:
function onOpen() {
var sheet = SpreadsheetApp.getActiveSheet();
var values = sheet.getRange("A:A").getValues();
var maxIndex = values.reduce(function(maxIndex, row, index) {
return row[0] === "" ? maxIndex : index;
}, 0);
sheet.setActiveRange(sheet.getRange(maxIndex + 2, 1));
}
Screen shot of Sheet 1, "LOGBOOK"
The second sheet in this file is titled "FLIGHT DUTY" In this sheet I want the same function to happen onOpen. I want the cursor to be placed in the first empty cell of column A as well. Again, I start column A with a date input for the given flight duty period. If I break this sheet out to a stand along Google Sheets file, this script works:
function onOpen(){
var sheet = SpreadsheetApp.getActiveSheet();
//Find the last cell with data in that specific column (A in this case)
var lastCell = sheet.getRange("A1").getNextDataCell(SpreadsheetApp.Direction.DOWN);
//Activate the next cell
lastCell.offset(1, 0).activate();
}
Screen shot of sheet 2 "Flight Duty"
In my current Google Sheets file, I want Sheet 1 "LOGBOOK" to onOpen, place my cursor to the first empty cell in Column A. At the same time, onOpen, I want Sheet 2 "FLIGHT DUTY" to place my cursor in the first empty cell of Column A of that sheet when I select that tab.
Below is a screen shot of the SHEET names at the bottom. The only sheets that I need a script for are 1 LOGBOOK and 2 FLIGHT DUTY.
Screen shot of Sheet Names
Here is a more stable version
function onOpen(){
var ui = SpreadsheetApp.getUi();
ui.createMenu('↓ Go to ... ↓')
.addItem('first empty row', 'goToFirstEmptyRow')
.addToUi();
var sh = event.source.getActiveSheet();
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function goToFirstEmptyRow(event){
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function getFirstEmptyDataRow(sheet) {
var range = sheet.getRange("A4");
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
}
You have a menu at the top right
I tested the below code. The last row will be activated in as many sheets as you have. Whichever sheet you open, you will always land at the bottom row. Takes one second to run.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var tabs= ss.getSheets();
for (i=0; i<tabs.length;i++){
var lr = tabs[i].getLastRow();
tabs[i].getRange("A"+(lr+1)).activate();
}
}
Try ...
function onOpen(event){
var sh = event.source.getActiveSheet();
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function onSelectionChange(event){
var sh = event.source.getActiveSheet();
sh.getRange('A'+getFirstEmptyDataRow(sh)).offset(1,0).activate();
}
function getFirstEmptyDataRow(sheet) {
var range = sheet.getRange("A4");
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
}
https://docs.google.com/spreadsheets/d/1Um9gnfz3VH6Vf0PwNVW8LOD3F63qb5h7PQl-hSYT_0E/copy

Google Sheet Script run when we edit on a particular sub sheet

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

How can i pause a google script until a cell has numeric value?

While a script imports values from another sheet i want to pause the rest of the script.
When the cell c11 becomes a numeric value (import succeeded) i want the script to replace the formula with a "value only" copy-paste of the cells.
Instead of the numeric check a pause would also be a solution.
The next time i run the script i put back the import formula in the cells and so on..
Thx in advance
function updateStartHierWaarden() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('C10').activate();
spreadsheet.getCurrentCell().setFormula('=importrange(verkoopfacturensysteem;"openstaandeFacturen!C2")');
spreadsheet.getRange('C11').activate();
var waarde = spreadsheet.getRange('C11').getValue();
spreadsheet.getCurrentCell().setFormula('=importrange("bank";"saldo!a1")');
var type = typeof waarde;
if (type == "number") {
spreadsheet.getRange('C10:C11').activate();
spreadsheet.getRange('C10:C11').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}else{
var ui = SpreadsheetApp.getUi();
ui.alert('geen nummer');
}
/* Utilities.sleep(10000);
var ui = SpreadsheetApp.getUi();
ui.alert('10sec pause');
if
*/
}

Trying to loop setFormula() through open sheets

I'm using an add-on (toTabs) that separates a sheet of data based on the value in column H. It generates a new sheet for each unique value in H2:H, and then places a filter() in cell A2 on each sheet. This works wonderfully, except my next step is to export each sheet to .csv, and I need to get rid of H:H before doing that.
I'm trying to write a script that changes each filter from A2:H to A2:G and either ignores the rest of the formula, or replaces the text that the filter searches for to the sheet name, then loops through every sheet except for specific sheets. I feel like I'm 95% of the way there, but the loop isn't working quite right. Here's what I have so far:
function Loop() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
// Array holding the names of the sheets to exclude from the execution
var exclude = ["Raw Input","Remove Chuff","Filtered Input","Final Input","Data Values","blank","totabs"];
for(var s in allsheets){
var sheet = allsheets[s];
// Stop iteration execution if the condition is meet.
if(exclude.indexOf(sheet.getName())==-1) continue;
var cell = s.getRange("A2");
cell.setFormula('=Filter(\'Data Values\'!A2:G,\'Data Values\'!H2:H="'+ SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName() + '")');
s.getRange('H:H').activate();
s.getActiveRangeList().clear({contentsOnly: true, skipFilteredRows: true});
s.getRange('F:F').activate();
s.getActiveRangeList().setNumberFormat('00000');
}
}
Here's a copy of the file with all the irrelevant tabs hidden. Any help would be welcome.
Try this:
function Loop() {
var ss=SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
var exclude = ["Raw Input","Remove Chuff","Filtered Input","Final Input","Data Values","blank","totabs"];
for(var i=0;i<allsheets.length;i++){
var sheet = allsheets[i];
if(exclude.indexOf(sheet.getName())!=-1) continue;
sheet.getRange("A2").setFormula('=Filter(\'Data Values\'!A2:G,\'Data Values\'!H2:H="'+ sheet.getName() + '")');
sheet.getRange(1,8,sheet.getLastRow(),1).clear({contentsOnly: true, skipFilteredRows: true});
sheet.getRange(1,6,sheet.getLastRow(),1).setNumberFormat('00000');
}
}

Copy row to another tab, getFormulas, clearContent, setFormulas

I'm trying to make google sheets automatically copy a row and paste it to the bottom of another tab when Completed is selected from a dropdown in the 8th column. Then I want it to grab the formulas from that row, clear the row, and then replace the formulas. The code works as far as copying and pasting to the other tab but I can't get it to grab the formulas, clear the row and then replace the formulas. I can't even get it to clear the row. The code is correctly executing everything but the last four lines.
This isn't my code. I got it from a website and modified it to fit my sheet. I have a very basic understanding of javascript. I need the original row to remain with just the formulas populated so that it can be reused. I'm using copyTo instead of moveTo because the row is being referenced upstream by another sheet and moveTo causes the formulas on the upstream spreadsheet to auto update to the new location of the data instead of continuing to reference the original row. Thank you for any help.
function myStorage() {
// moves a row from a sheet to another when a value is entered in a column
var sheetNameToWatch = "In Progress";
var columnNumberToWatch = 8;
var valueToWatch = "Completed";
var sheetNameToMoveTheRowTo = "Storage";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).copyTo(targetRange);
// this is where it stops working. I'm not sure how to grab just the row of the active cell and do the following
var clearRange = sheet.getRange(range.getRow());
clearRange.getFormulas();
clearRange.clearContent();
clearRange.setFormulas();
}
}
Try this:
function CallItSomethingElse(e) {
var sh=e.range.getSheet();
if (sh.getName()=="In Progress" && e.range.columnStart==8 && e.value=="Completed") {
var targetSheet = e.source.getSheetByName("Storage");
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).copyTo(targetRange);
var clearRange=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn());
var fA=clearRange.getFormulas();//returns a 2d array of formulas
clearRange.clearContent();
clearRange.setFormulas(fA);//you have to add that array back into this line
}
}
Because many people will take this code and try to run it from the editor just to return and tell me that they get the error of cannot find method getSheet of undefined. Let me first suggest that you can't run this code without an event object. The single e is the parameter that the event object will be place into. Without the e the program cannot run. Also unless there is a trigger to create the e the script cannot run. So if you wish to run this from the script editor then you will have to provide the event object. I don't choose to do that so when I debug these things I do it will a live trigger.