How to exclude the first and second rows - Apps Script / Google Sheets - javascript

Code I have allows me to automatically update the formulas in the sheet. It works.
On 1st row I have headers and they do not overwrite each other. In the 2nd I have the average formulas. Each edit overwrites entire column. Also those averages.
So I would like to exclude also 2nd row from pasting formulas.
function onEdit(e) {
var activeSheet = e.source.getActiveSheet()
var tabs = ['2023'];
if(tabs.indexOf(activeSheet.getName()) !== -1){
var cell = e.range;
var col = cell.getColumn();
if(cell.getFormula() !==""){
var destination = activeSheet.getRange(3, col, activeSheet.getLastRow()-2, 1);
cell.copyTo(destination);
}
}
}
I ask for your help.
I have tried to exclude 2nd row in the code, with no effect.

Related

Dynamically edit columns in google sheets

Context
I am creating a database of stock price data. I am currently using the below onEdit function:
function onEdit(e) {
// Get the spreadsheet that this will apply to, the tabs and columns
var ss = e.source.getActiveSheet();
var excludeTabs = ["Summary", "FTSE 250"];
var excludeColumns = [1,2,12,13,14,15,16]; // the columns to isolate for special reasons
var excludeCells = ["M1","I1","I2"];
// What is the criteria for action to be taken? If the terms defined in excludeTabs are met, then move onto the next criteria (indexOf used because we stored it as an array
if(excludeTabs.indexOf(ss.getName())===-1){
// The range from the spreadsheet.
// Scripts expects an event in a cell
var cell = e.range;
// For the entire column of this cell
var col = cell.getColumn();
// Within the universe of tabs that this script applies to, we want to exclude the columns in the array
if(excludeColumns.indexOf(col)===-1)
// Within the universe of tabs and columns, we want to exclude certain cells
if(excludeCells.indexOf(cell)===-1)
// Need to make sure it only applies to formulas
if(cell.getFormula() !== ""){
var destination = ss.getRange(4, col, ss.getLastRow()-1, 1);
cell.copyTo(destination);
}//End of the remaining universe of data taking out the exceptions
}//End Tab criteria
}//End function
This allows for an edit in some of the columns to be performed when I edit the cell. So far it works but with a few kinks.
Problem 1
Sometimes, when I edit a cell above the fourth row of a column, it edits the entire column despite me telling it to start from the fourth row. This happened just a few minutes ago in a cell I told it to exclude above "I2". Is there anything wrong with the code I have written to this effect?
Problem 2
I tried creating other exceptions for the code, where for some specified ranges, it will only edit from a different cell range. Not the fourth cell of every column but of say the 10th cell. I tried adding it below var destination = ss.getRange(4, col, ss.getLastRow()-1, 1) but it did not work. I also tried creating a separate onEdit function for a different cell location but it also did not work.
So far I have been using the sheet formulas like the below:
IFERROR(IFS(C4="Returns","",AND(C4="",C5="",C6="",C7="",C8=""),"",AND(ISNUMBER(C4),ISNUMBER(C5),ISNUMBER(C6),ISNUMBER(C7),ISNUMBER(C8)),COVAR($C4:$C8,'FTSE 250'!J5:J9)),""))
But this just gets messy. Sometimes there is data in the cell and so it would render formulas like the above useless. An example is the below picture.
Update I want the onEdit to start and drag down from the 10th row of the column but only for that column (this is the row in that column that I will be editing). I also want to be able to do this for other columns (start the automatic copy down process from different rows).
This is the range I am trying to edit
[
Update 2
...
if(!excludeTabs.includes(ss.getName()) &&
!excludeColumns.includes(col) &&
!excludeCells.includes(cell.getA1Notation()) &&
cell.getFormula() !== ""
){
if(col==33){
var destination = ss.getRange(8, col, ss.getMaxRows()-7, 1);
cell.copyTo(destination);
}
else if(col===30){
var destination = ss.getRange(8, col, ss.getMaxRows()-7, 1);
cell.copyTo(destination);
}
else{
var destination = ss.getRange(4, col, ss.getMaxRows()-3, 1);
cell.copyTo(destination);
}
}
Explanation:
Issues:
Your if statements don't have closed brackets with code in it.
Here if(excludeCells.indexOf(cell)===-1) there is a problem:
var excludeCells = ["M1","I1","I2"]; is an array of strings and var cell = e.range; is a range object. You are actually comparing two different things (a string vs a range object.)
Instead you want to replace: if(excludeCells.indexOf(cell)===-1) with if(excludeCells.indexOf(cell.getA1Notation())===-1).
Improvements:
Instead of using multiple if statements which at the end lead to one single code block, use one if statement with multiple conditions.
Also this range getRange(4, col, ss.getLastRow()-1, 1); does not make a lot of sense either. It makes more sense to use ss.getLastRow()-3 because you are starting from 3.
Instead of using excludeCells.indexOf(cell.getA1Notation())===-1 which is a long expression, you can use includes() like that !excludeCells.includes(cell.getA1Notation()).
Solution:
function onEdit(e) {
var ss = e.source.getActiveSheet();
var excludeTabs = ["Summary", "FTSE 250"];
var excludeColumns = [1,2,12,13,14,15,16]; // the columns to isolate for special reasons
var excludeCells = ["M1","I1","I2"];
var cell = e.range;
var col = cell.getColumn();
if(!excludeTabs.includes(ss.getName()) &&
!excludeColumns.includes(col) &&
!excludeCells.includes(cell.getA1Notation()) &&
cell.getFormula() !== ""
){
if(col==33){
var destination = ss.getRange(10, col, ss.getMaxRows()-9, 1);
cell.copyTo(destination);
}
else{
var destination = ss.getRange(4, col, ss.getMaxRows()-3, 1);
cell.copyTo(destination);
}
}
}
Please Note:
getLastRow() returns the last row with content. For example if you have 10 columns and the first 10 have last row with content to be 55 but there is a random value in column 20 at the end of the sheet let's say row 900 then 900 will be the last row in your sheet. Be careful with that, otherwise you will need other approach to get the last row with content. Formulas are content too. So a formula all the way to the bottom of the sheet might determine what getLastRow returns.
Try this:
function onEdit(e) {
var sh = e.range.getSheet();
var excludeTabs = ["Summary", "FTSE 250"];
var excludeColumns = [1,2,12,13,14,15,16];
var excludeCells = ["M1","I1","I2"];
if(excludeTabs.indexOf(sh.getName())==-1 && excludeColumns.indexOf(e.range.columnStart)==-1 && excludeCells.indexOf(e.range.getA1Notation())==-1 && e.range.getFormula()!=""){
e.range.copyTo(sh.getRange(4, e.range.columnStart, sh.getLastRow()-3, 1));//The numbers of rows to the bottom is sh.getLastRow()-3 -1 will have to roll off of the bottom of the spreadsheet which will give you out of range errors
}
}
}

Google Sheets Script that will adjust (add or remove) rows based on certain conditions

I'm working on a Google Sheet table that is pulling data from another sheet. The table I'm working is using UNIQUE FILTER function so that I can only show a list based on certain conditions. Some conditions require more list thus require more rows and some conditions requires less thus less rows. I want to adjust the number of rows on my table based on these.
I came up with this script to delete blank rows, the problem is that it's only deleting blank rows AFTER the last row that has values. It's not deleting blank rows in between rows with values.
function removeEmptyRows() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
for (var s in allsheets) {
var sheet = allsheets[s]
var maxRows = sheet.getMaxRows();
var lastRow = sheet.getLastRow();
if (maxRows - lastRow != 0) {
sheet.deleteRows(lastRow + 1, maxRows - lastRow);
}
}
}
I also cant seem to make a script that will automatically append new rows when a condition results to more data and row requirements.
TLDR;
Need a script that will automatically delete empty rows
Need a script that will automatically add rows and copy top rows formula
Here's a sample sheet I created to better understand what I'm saying: https://docs.google.com/spreadsheets/d/1VU8PsCGcomh3xf3BMI2TUNbnE8UI1RmRpPg4tdrG9Ow/edit?usp=sharing
To delete empty rows inbetween you need to query for all rows either they are empty
A way to do it is to take the first cell (column A) of each row of the data range and define that if A is empty, then the whole rows shall be considered as empty.
Sample:
function removeEmptyRows() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
for (var s in allsheets) {
var sheet = allsheets[s];
var data = sheet.getDataRange().getValues();
for(var i = data.length; i > 0; i--){
var maxRows = sheet.getMaxRows();
var lastRow = sheet.getLastRow();
if (maxRows - lastRow != 0) {
sheet.deleteRows(lastRow + 1, maxRows - lastRow);
}
var maxRows = sheet.getMaxRows();
var lastRow = sheet.getLastRow();
if (data[i-1][0] == "") {
sheet.deleteRow(i);
}
}
}
}
You need to judge either it is a suitable criterium for you.
Maybe in your case the cell in column A might be empty, but the rows has entries in other columns. In this case you need to decide which is the critical column to decide that the whole rows is empty - or test all columns of each row - which would make your code slower.

How to paste values from one sheet to another to last row of specific column

A novice on app scripts, but managed to successfully build my own script through much research, but now my script is running into errors. Below is my current code:
function MyFunction() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Daily Sales');
var range = ss.getRange('B8:H83');
var data = range.getValues();
var OUrange = ss.getRange('K8:Q83');
var OUdata = OUrange.getValues();
var ts = sss.getSheetByName('Tracker');
ts.getRange(ts.getLastRow()+1, 1,data.length, data[0].length).setValues(data);
ts.getRange(ts.getLastRow()+1, 1,OUdata.length, OUdata[0].length).setValues(OUdata);
}
In the Daily Sales sheet I am copying values from columns B-H and K-Q and pasting them in the last row of the Tracker sheet starting at Column A. The Daily Sales values in Col. K-Q are pasted correctly below the B-H values, so happy with those results.
On the Tracker sheet these values are in Columns A-G. However I have since added formulas in Columns I and J based on the script data and a manual entry in Column H. These formulas are copied down the entire column within the sheet (i.e. row 5000). Since adding these formulas, the script is now pasting values in A5001.
I realize it is because of these formulas, but is there a way to update my script to paste in the last row of column A while maintaining those formulas in Columns I and J? Thanks in advance.
You could create a helper function that computes the first free row of a given column (NOT a column with formulas)
function MyFunction() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Daily Sales');
var range = ss.getRange('B8:H83');
var data = range.getValues();
var OUrange = ss.getRange('K8:Q83');
var OUdata = OUrange.getValues();
var ts = sss.getSheetByName('Tracker');
var values = data.concat(OUdata).filter(function (r) {return r[0]});
var fr = findFirstFreeRow(ts, 1) // second parameter represents column to check for first free row
ts.getRange(fr, 1,values.length, values[0].length).setValues(values);
}
function findFirstFreeRow(sheet, colNum) {
var v = sheet.getRange(1, colNum, sheet.getLastRow()).getValues(),
l = v.length,
r;
while (l >= 0) {
if (v[l] && v[l][0].toString().length > 0) {
r = (l + 2);
break;
} else {
l--;
}
}
return r;
}
See if that helps?
After deleting the extra empty rows from your 'Tracker' sheet, you can use the appendRow function to create a new row with the manual values (leaving the cells that need formulas blank).
After the manual values are in, you can then get the cells that need formulas and use setFormula on them.
For the sake of brevity, if you wanted column A,B,C,E,F to have manual values and column D to have a formula you would do:
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(['A','B','C','','E','F']);
sheet.getRange(sheet.getLastRow(),4).setFormula('=FORMULA(HERE)');

Google app script - looping through the rows in a spreadsheet

I am trying to loop through rows within a spreadsheet and identify if a particular row has the key word "hello" and move that entire row into a new spreadsheet.
I have attempted the following code. The code works for the first row but doesn't loop through and stops after the first row. Expanding the range selection to "C1:E32" does not help.
function Edit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activatedSheetName = ss.getActiveSheet().getName();
var ActiveSheet = ss.getSheetByName("ActiveSheet"); // source sheet
var MoveDatatoThisSheet = ss.getSheetByName("MoveDatatoThisSheet"); // target sheet
var re = new RegExp(/(Hello)/i);
var startRow = 1;
var endRow = ss.getLastRow();
var getRange = ss.getDataRange();
var getRow = getRange.getRow();
for (var ree = startRow; ree <= endRow; ree++) {
// if the value in column D is "Approved", move the row to target sheet
cellValue = ss.getRange("C1:E1");
if (cellValue.getValue().match(re)) {
// insert a new row at the second row of the target sheet
MoveDatatoThisSheet.insertRows(2, 1);
// move the entire source row to the second row of target sheet
var rangeToMove = ActiveSheet.getRange(/*startRow*/ getRow, /*startColumn*/ 1, /*numRows*/ 1, /*numColumns*/ ActiveSheet.getMaxColumns());
rangeToMove.moveTo(MoveDatatoThisSheet.getRange("A2"));
// add date and time of when approved to target row in column E
MoveDatatoThisSheet.getRange("E2").setValue(Date());
// delete row from source sheet
ActiveSheet.deleteRow(cellValue, 1);
}
}
}
Your loop never uses the variable ree, it only operates with cellValue = ss.getRange("C1:E1").
Another problem is that deletion shifts the rows under the deleted one, possibly causing subsequent operations to act on a wrong row. When you go through an array of rows, deleting some of them, do it bottom up, not top down.
for (var ree = endRow; ree >= startRow; ree--) {
var rangeToCheck = ss.getRange(ree, 3, 1, 3); // 3 columns starting with column 3, so C-E range
if (rangeToCheck.getValues()[0].join().match(re)) { // joining values before checking the expression
MoveDatatoThisSheet.insertRows(2,1);
var rangeToMove = ActiveSheet.getRange(/*startRow*/ getRow, /*startColumn*/ 1, /*numRows*/ 1, /*numColumns*/ ActiveSheet.getMaxColumns());
rangeToMove.moveTo(MoveDatatoThisSheet.getRange("A2"));
// add date and time of when approved to target row in column E
MoveDatatoThisSheet.getRange("E2").setValue(Date());
// delete row from source sheet
ActiveSheet.deleteRow(ree);
}
}
If the goal is to check only column D (say), the code simplifies slightly
var rangeToCheck = ss.getRange(ree, 4); // column D in row ree
if (rangeToCheck.getValue().match(re)) { // joining values before checking the expression
Performance
As Google recommends, one should avoid multiple calls to getValues / setValues and such, instead grabbing all necessary data at once, processing it, and making batch changes at once. E.g., instead of placing it a row in another sheet, add it to an array; when the loop ends, place the entire array in that sheet.

Move row back and forth between tabs when a cell is edited

I am making a lending library for books with one sheet to show what is currently borrowed (on sheet Borrowed). Every other sheet is someone's name (called Name in my example), showing what they own that can be borrowed. I want to copy the row to the Borrowed sheet when "Borrowed" is in column C, and when it is changed back to "Returned" from either Borrowed or Name, remove it from the Borrowed sheet. So they need to be linked somehow. I had tried using query= before but couldn't edit the status of the book on Borrowed, it would just break the row.
This code is working to get the row copied over to Borrowed, but I'm not sure how to accomplish the returned part.
function onEdit(event) {
// source data in sheet named Name
// target sheet named Borrowed
// test column with yes/no is col 3 or C
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Name" && r.getColumn() == 3 && r.getValue() == "Borrowed") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Borrowed");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1,1,numColumns);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
}
Here is a hybrid approach, which uses spreadsheet functions in one direction (from Master sheet to Borrowed), and a script to move things in the opposite direction.
The Borrowed sheet contains the formula
=iferror(filter({row(Master!A:C), Master!A:C}, Master!C:C = "Borrowed"), "No books are borrowed at present")
This displays the records of borrowed books (assumed to be in columns A:C), preceded by the row number of each book in the master sheet. The row number helps in linking the records between sheets. (As a side effect, the data in Borrowed sheet moves to the right by one column).
If book status is changed to Returned from Master sheet, it goes away from Borrowed automatically. The tricky part is dealing with the edits to Borrowed sheet. This is what this script is for.
function onEdit(e) {
var ss = e.source;
var sheet = e.range.getSheet();
var row = e.range.getRow();
var col = e.range.getColumn();
var entry = (typeof e.value == 'object' ? '' : e.value.toLowerCase());
if (sheet.getName() == 'Borrowed' && col == 4 && entry == 'returned') {
e.range.clear();
SpreadsheetApp.flush();
var originalRow = sheet.getRange(row, 1).getValue();
ss.getSheetByName('Master').getRange(originalRow, 3).setValue('Returned');
}
}
The script checks that the user indeed put "Returned" (case insensitive) in column 4 (D) of sheet Borrowed. If so, it notes the row number and clears the edited cell. This restores the functionality of filter function, returning the sheet to its previous state. The script then reads the original row number from column A of Borrowed, and edits the appropriate cell of Master sheet. As a result of this edit, the Borrowed sheet no longer has the row with the returned book.
Yes, this is a somewhat roundabout way to do things... but it works.

Categories

Resources