Range Length in Google Apps Script - google-apps-script

I want to run a script that copies a sheet data to a master sheet (append all my sheets).
The first part of copying and pasting is working but I want to add a column which tells me the name of the origin sheet. I wrote a loop for it but nothing is happening when I executing the script (only the copy and paste). This is my whole code:
function appendSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var reportLastRow = sh.getLastRow()
var reportLastColumn = sh.getLastColumn()
var reportData = sh.getSheetValues(3,1,reportLastRow,reportLastColumn);
var recordsSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("2020 Data");
var lastRow = recordsSheet.getLastRow();
//var recordLastRow = sh.getLastRow();
var recordLastColumn = recordsSheet.getLastColumn();
var reportSheetName = sh.getSheetName();
recordsSheet.getRange(lastRow + 1,1,reportLastRow,reportLastColumn).setValues(reportData);
var arrayLength = (lastRow - reportData.length);
for (var i = 0 ; i <= arrayLength ; i ++) {
var taskDateCell = recordsSheet.getRange(arrayLength - i, recordLastColumn);
taskDateCell.setValues(reportSheetName);
}
}

Your code has several problems you should fix
Be aware of the fact that the method getRange() expects the syntax firstRow, firstColumn, numRows, numColumns - not firstRow, firstColumn, lastRow, lastColumn. You need to adjust your range and your function getSheetValues() (I assume it is you custom funciton based on the method getRange() accordingly.
If you assign a value to one cell at a time taskDateCell, you should use setValue() instead of setValues()
It seems like your definition of arrayLength might not be right. Test it by logging it.
Your main problem:
You define:
for (var i = arrayLength ; i < arrayLength ; i ++)
In other words:
Set i to arrayLength and iterate while i is smaller than arrayLength.
This condition is never fullfilled, and thus the number of iterations will be zero.
As a genral advice: Implement in your code many logs - to visualize important values, such a range notations and length of arrays, or counter variables in a loop - this will help you to find bugs faster.

Explanation:
First of all I optimized your code. You have unnecessary lines of code which make your code difficult to be understood but also slow. The optimizations involve reducing the number of lines to the minimum, defining constant variables, making the variable names more descriptive and finally getting rid of the for loop by replacing it with a more efficient approach.
Another correction would be at this line: sh.getSheetValues(3,1,reportLastRow,reportLastColumn); Here you are starting from the third row but you are getting two rows extra; reportLastRow is the number of rows you want to get but since you are starting from the third row you need to deduct 2.
To answer your question, one way to solve your problem is to set the name of the report to the last column of the records sheet where you entered the data from the report sheet. Since you want to add the same value (sheet name) to every row, you can select a 2D range but use setValue.
I am not a big fan of getActiveSheet in const report_sh = ss.getActiveSheet(); since this line is assuming that you have selected the desired sheet (report sheet) in the UI. Please be careful with that, otherwise change that line to something like that:
const report_sh = ss.getSheetByName('report sheet');
and of course adjust 'report sheet' to the name of the sheet you want to append.
Solution:
function appendSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const records_sh = ss.getSheetByName("2020 Data");
const report_sh = ss.getActiveSheet();
const reportData = report_sh.getSheetValues(3,1,report_sh.getLastRow()-2,report_sh.getLastColumn());
const records_lr = records_sh.getLastRow();
records_sh.getRange(records_lr+1,1,reportData.length,reportData[0].length).setValues(reportData);
SpreadsheetApp.flush();
records_sh.getRange(records_lr+1,records_sh.getLastColumn()+1,reportData.length,1).setValue(report_sh.getSheetName());
}

Related

Trying to retain formatting in a sheet with multiple users

Very new at this and I'm probably doing something very basic wrong but I hope someone can help.
I'm trying to preserve the formatting of a sheet that is open for editing by multiple users. I'm using the onEdit() trigger and using a second sheet (formatSheet) to restore the formatting when a user changes the values.
I'm working through this piecemeal checking that each line works so this is not complete (ie I still need to ensure data validation and conditional formatting is retained, I'm not sure that copyFormatToRange does this). However the copyFormatToRange command does not work, script stops executing on this line.
// Retain Formatting
var formatSheetName = "Master Format";
var formatSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(formatSheetName);
var watchSheetRange = SpreadsheetApp.getActiveSpreadsheet().getActiveRange();
var formatSheetRange = formatSheet.getRange(watchSheetRange.getA1Notation());
var firstRow = formatSheetRange.getRow();
var lastRow = formatSheetRange.getLastRow();
var firstColumn = formatSheetRange.getColumn();
var lastColumn = formatSheetRange.getLastColumn();
var gridID = watchSheetRange.getGridId();
watchSheetRange.copyFormatToRange(gridID,firstRow,firstColumn,lastRow,lastColumn);
I've checked that watchSheetRange is in fact a Range (it is) that gridID is a gridID (it is), that the row/column variables are integers (they are).
Any help appreciated.
This works
function lfunko() {
const ss = SpreadsheetApp.getActive();
const fsh = ss.getSheetByName("Sheet0");
const wsh = ss.getSheetByName("Sheet1")
const frg = fsh.getRange("A3:S3");//cols 1 to 19
let wrg = wsh.getActiveRange();
let id = wrg.getGridId();
frg.copyFormatToRange(id, 1, 19, wrg.getRow(), wrg.getRow());
}
I'm using the same columns and whatever the active row and I'm only doing one row. The params for the copyFormatToRange are id, columnStart, columnEnd, rowStart, rowEnd
Have you considered creating a form instead of sharing a spreadsheet for data input? Form responses automatically appear in a separate tab in the spreadsheet in a row-oriented fashion and are thus easy to process with spreadsheet functions such as query() and filter().

Copying Multiple Rows from One Sheet to A Target - Google Apps Script (...is not a function error)

I am trying to write a script that will copy specific rows on a general source sheet and paste it to a target sheet. I know that there is a lot of examples of this online but there are some variations that do not work with my code, so it is difficult for me to apply (a novice).
Here is the code that I have written:
function singleSheetIOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = ss.getLastRow();
var fillDownRange = ss.getRange(2, 19, lr-3);
ss.insertColumnAfter(18);
ss.getRange(1,19,1,1).setValue('IOD');
ss.getRange("S2").setFormula("=H2*R2");
ss.getRange("S2").copyTo(fillDownRange);
const netSquare = ss.getRange('S2:S').getValues().flat().filter(v=>v!='');
const sum = netSquare.reduce((a,b)=>a+b);
ss.getRange(ss.getLastRow(), 20, 1, 1).setValue(sum);
const value1 = ss.getRange(lr, 20).getValue();
const value2 = ss.getRange(lr,9).getValue();
const divisor = value1/value2;
ss.getRange(lr,11,1,1).setValue(divisor);
%below this is the actual "copy and paste" attempt
var lastTwoRows = ss.getRange(lr-1,1,2,11).getValues();
var pastesheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Summary');
pastesheet.setValues(lastTwoRows);
}
I keep receiving this error: TypeError: pastesheet.setValues is not a function
It is important that I do not define the source Sheet by name (only the target sheet) because this is nested in a for loop that will loop across all other sheets in the spreadsheet.
Does anyone have an idea why I might be receiving this error?
Thank you so much.
AEB
You must define the range of the sheet you're looking to setValues on.
// Currently:
pastesheet.setValues(lastTwoRows);
// Needs to be:
pastesheet.getRange(...).setValues(lastTwoRows);
As of right now, you're trying to set the values on a sheet, but have not specified where to put them.
Note: The range must be equal to the size of the array you're setting.
See:
.getRange(row, column, numRows, numColumns)
.getRange(a1Notation)

Correct use of getRange and getLastRow for source and target ranges

If someone could help me understand getRange please. I have read many responses on here and the documentation here but am still confused as to how it works. (I'm a beginner coder as you can see)
How do I reference a specific row in a data set with getRange?
I have a table of data in A1:D10
I would like to copy one row A5:D5
I have tried the below but get an error. "number of rows in data does not match the number of rows in the range". Could someone explain how to reference a specific row?
function copyPaste() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName("Sheet1");
// this is me referencing that row
var sourceRange = source.getRange("A5:D5");
// the rest
var data = sourceRange.getValues();
var lr = sourceRange.getLastRow();
var lc = sourceRange.getLastColumn();
var target = ss.getSheetByName("Sheet2")
target.getRange(target.getLastRow()+1,1,lr,lc).setValues(data);
}
Issue:
You should provide the number of rows and columns when calling getRange(row, column, numRows, numColumns).
You are providing the indexes of the last row and column of A5:D5 (that is, lr=5, lc=4), when you should provide the number of rows and columns of the source range (lr=1, lc=4). Because of this, the source and destination ranges don't match dimensions, and you are getting this error.
Solutions:
Therefore, this can be fixed by changing these two lines:
var lr = sourceRange.getLastRow();
var lc = sourceRange.getLastColumn();
To these ones:
var lr = sourceRange.getNumRows();
var lc = sourceRange.getNumColumns();
An alternative option would be to use the data dimensions (using length):
target.getRange(target.getLastRow()+1, 1, data.length, data[0].length).setValues(data);
Reference:
getNumRows()
getNumColumns()

Copy a variable range depending on the number of non-empty cells

From a google sheets macro, I am trying to copy a variable range of cells, which depends on the amount of non-empty cells each time. In the following sentence:
spreadsheet.getRange ('Orders! 1: 30'). copyTo (spreadsheet.getActiveRange (), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
I need that instead of the number "30" there is a variable that is defined according to how many lines are occupied in column I of the same sheet.
I was thinking of using the "for" function to scan lines in column I from the cell I1 until It finds an empty one (or not greater than 0). but I'm not sure how to do this.
After the help of #Iamblichus and #Tedinoz I was able to advance in the code, but now a connected and similar problem arised.
I need to paste the selected range in a different tab, but in which same variable no longer recognizes same amount. This is my code:
function Test() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName("Aux");
spreadsheet.getRange('I3:I').createFilter();
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(['0']).build();
spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(9, criteria);
var valuesI = spreadsheet.getRange("I1:I").getValues();
var numRowsI = valuesI.filter(String).length;
var firstRow = 1;
var firstCol = 1;
var numCols = sheet.getLastColumn();
var originRange = sheet.getRange(firstRow, firstCol, numRowsI, numCols);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Hist_D'), true);
spreadsheet.getActiveSheet().insertRowsBefore(sheet.getActiveRange().getRow(), numRowsI);
spreadsheet.getRange('A1').activate();
originRange.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_FORMAT, false);
originRange.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Aux'), true);
spreadsheet.getRange('T35').activate();
spreadsheet.getActiveSheet().getFilter().remove();
}
"Hist_D" is a record in which the latest data is added at the top. I need to copy a variable amount X of lines from the "Aux" tab to the first lines of the "Hist_D" tab. But before that I need to add (insertRowsBefore) the same number X of lines in "Hist_D" so that the old info is not overwritten. My problem now is how to keep the amount X in the numRowsI variable when changing tabs.
The number of lines inserted through the formula .insertRowsBefore is around 100 (total number of lines in the first tab), when it should be around 20 (number of non-empty lines in the first tab). According to my interpretation, for some reason the variable numRowsI changes when changing tab.
Update:
Assuming that:
For each row to be copied, you want to copy all columns with content and not just column I.
You want to copy this to the top of another sheet, without overwriting previous data.
You can try using this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var originSheet = ss.getSheetByName("Aux");
var destSheet = ss.getSheetByName("Hist_D");
var valuesI = originSheet.getRange("I1:I").getValues(); // As far as I can see, you want to check column I, not B
var numRowsI = valuesI.filter(String).length;
var firstRow = 1;
var firstCol = 1;
var numCols = originSheet.getLastColumn();
var originRange = originSheet.getRange(firstRow, firstCol, numRowsI, numCols);
destSheet.insertRows(1, numRowsI);
var destRange = destSheet.getRange(firstRow, firstCol, numRowsI, numCols);
originRange.copyTo(destRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES);
}
The main thing here is finding the last cell with content in a specific column, something that can be achieved using this (assuming there are no blank cells in between, which, considering your question, seems to be the case).
The number of blank rows you have to add to the top Hist_D in order not to overwrite existing data equals the number of rows that have been copied from Aux (that is, equal to numRowsI). You are having problems because getRange is a method of the Sheet class, not the Spreadsheet class.
I hope this is of any help.
Props to #Mogsdad ref and AndyE before him, for this incredibly handy method.
var Bvals = ss.getRange("B1:B").getValues();
var Blast = Bvals.filter(String).length;
The value of "Blast" is the number of cells in Column B that contain values - there is an implicit assumption that it is a contiguous range.
If you define your range using getRange(row, column, numRows, numColumns), then substitute "Blast" for "numrows".

Use script to update column but only to last row

I am trying to have a script run that will basically find the row id in my spreadsheet then copy and paste that ID as a value instead of the formula. My solution feels clunky and I've run into a couple of issues.
The main issue is that I only want the script to update active rows (ie rows that have content in them), which is variable. This sheet is updated constantly throughout the day, and I really only want it to find and write the row id once per day (basically, these are tasks to be handed out and the row id sets priority). My plan is once I get the find row id/paste value scripts going, I'll use a trigger to run daily.
I'm a bb when it comes to coding and understanding scripts, so maybe explain it to me like im 5 if you can.
function getRowId(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange("I2:I");
cell.setFormula("=row()");
}
function pasteValue(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("Sheet1");
var sheet = ss.getSheets();
var range = sourceSheet.getRange("I2:I");
range.setValues(range.getValues());
}
You can do the whole process in just one function. Here is the code I tested and worked successfully pasting the correlated row number in the "I" column of each row.
function pasteValue(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("Sheet1");
var range = sourceSheet.getDataRange().getValues();
for(var i=0; i<(range.length - 1); i++) {
var cell = sourceSheet.getRange(2 + i, 9);
cell.setValue(2 + i);
}
}
This code will do it only for the range of rows where is data already present, because it's using the getDataRange() function to make it more efficient. If you want to set the row number for all the rows present (even if they don't have any data), you need to change the declaration of the "range" variable to this:
var range = sourceSheet.getRange("I2:I");