Google sheet script auto add new rows with copied formulas - javascript

I need to create function which adds rows with copied formulas from above rows. After the script is launched it should result in accurate number (set 5 i this code) of blank rows at the end of the sheet.
The code I managed to create counts what number of rows should be added but adds only one row with copied formulas at the end.
Please help me edit this code to multiple the result of the function by "rowstoadd" parameter.
Sheet image
function autoaddRows() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Harmonogram');
var range = sheet.getRange("B2:B").getValues();
var lastRowB = range.filter(String).length + 2;
var lastRowA = sheet.getLastRow();
var blanknrows = sheet.getLastRow() - lastRowB;
if (blanknrows < 5) {
let rowstoadd = 5 - blanknrows;
Browser.msgBox(rowstoadd);
let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
let lastRowIndex = sheet.getLastRow();
let existingRange = getRowRange(sheet, lastRowIndex);
sheet.insertRowAfter(lastRowIndex);
let newRange = getRowRange(sheet, ++lastRowIndex);
existingRange.copyTo(newRange);
newRange.setFormulas(newRange.getFormulas());
newRange.clearNote();
}
function getRowRange(sheet, rowIndex) {
return sheet.getRange(rowIndex, 1, 1, sheet.getLastColumn());
}
}

I believe your goal is as follows.
You want to keep 5 new empty rows.
For example, when the last row of column "A" is 10 and the last row of column "B" is 9, you want to add 4 new rows.
Also, you want to put the formula to the column "B" of the inserted rows.
In this case, how about the following modified script?
Modified script:
function autoaddRows() {
var addRows = 5;
var sheet = SpreadsheetApp.getActive().getSheetByName('Harmonogram');
var range = sheet.getRange("B2:B").getValues();
var lastRowB = range.filter(String).length + 1;
var lastRow = sheet.getLastRow();
var blanknrows = lastRow - lastRowB;
var diff = addRows - blanknrows;
if (diff > 0) {
sheet.insertRowsAfter(lastRow, diff);
// var range = sheet.getRange("B" + lastRowB);
// range.copyTo(range.offset(1, 0, diff + 1), SpreadsheetApp.CopyPasteType.PASTE_FORMULA, false);
var numberOfCol = sheet.getLastColumn() - 1;
var range = sheet.getRange(lastRowB, 2, 1, numberOfCol);
range.copyTo(range.offset(1, 0, diff + 1, numberOfCol), SpreadsheetApp.CopyPasteType.PASTE_FORMULA, false);
range.clearNote();
}
}
In this modification, the difference between the last rows between column "A" and column "B" is retrieved. And the empty rows are inserted using the difference.
In your script, newRange.clearNote(); is used. So, I also add range.clearNote();. If you want to remove it, please remove it.
References:
insertRowsAfter(afterPosition, howMany)
copyTo(destination, copyPasteType, transposed)

Related

SpreadsheetApp.getLastRows() provides empty records and not truncate to those with data

I'm new to JS and use of Google App Script, so pardon my lack of JS knowledge.
I got a simple .gs script to work. It converts content in one cell to a hyperlink (e.g. if text is "blah", it would be "https://example.com/blah").
The script retrieves the number of rows to operate on. For one sheet, this does it correctly and truncates it to the last entry (row=31) even though the sheet has 1000 rows. However, for another sheet, it does not and returns 999:
Here's the debug for when it wasn't right:
Apr 8, 2022, 11:20:53 AM Debug doiColumn: 10 numRows: 999
Here it is for another sheet that worked as intended:
Apr 8, 2022, 9:44:47 AM Debug doiColumn: 5 numRows: 31
The docs say: "Returns the position of the last row that has content."
For illustration, here is a snippet of the code:
let spreadsheet = SpreadsheetApp.getActive();
let sheet = SpreadsheetApp.getActiveSheet();
const column = getColumn(sheet, column_name);
let range = sheet.getRange(2, column, sheet.getLastRow() - 1, 1)
console.log(range.getValues());
const numRows = range.getNumRows();
console.log(`column: ${column} numRows: ${numRows}`)
I would like to make sure that .getLawRow() is truncated. Otherwise this script runs longer than it should.
Sorry I could not fit it into a comment
It requires col = column number, sh = sheet (not the name) ,ss = Spreadsheet (not the name)
function getColumnHeight(col, sh, ss) {
var ss = ss || SpreadsheetApp.getActive();
var sh = sh || ss.getActiveSheet();
var col = col || sh.getActiveCell().getColumn();
var rcA = [];
if (sh.getLastRow()){ rcA = sh.getRange(1, col, sh.getLastRow(), 1).getValues().flat().reverse(); }
let s = 0;
for (let i = 0; i < rcA.length; i++) {
if (rcA[i].toString().length == 0) {
s++;
} else {
break;
}
}
return rcA.length - s;
//const h = Utilities.formatString('col: %s len: %s', col, rcA.length - s);
//Logger.log(h);
//SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(h).setWidth(150).setHeight(100), 'Col Length')
}
There are other ways to do it but I prefer this one. It's been reliable for me
Using ranges like this sheet.getRange("A1:Z") will also provide you will a bunch of nulls from getLastRow() to getMaxRows() which then need to be filter out but if you do have a null within your data then that will also get removed and now you have bogus data because that row being removed now messes up your row order.
Try to replace .getLastRow() by
.getLastDataRow(column)
and add this prototype function
Object.prototype.getLastDataRow = function(col){
var lastRow = this.getLastRow();
if (col == null){col=1}
var range = this.getRange(lastRow,col);
if (range.getValue() !== "") {
return lastRow;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
};

Google Sheets - Moving row to another sheet based on specific column

Very new to the Google Sheets script editing, but I'm trying to move rows between sheets. I've done this successfully using getLastRow however now I'm attempting to do this based on a specific column. The reason for this is that the sheet the info is heading to has preset data validation fields and checkboxes, resulting in the row going to row 1000 every time.
I'm looking for when an option is selected on the dropdown menu on the current sheet, it will find the first free row based on a certain column, and replace that row (which is essentially empty except for the preset fields).
Currently I have as follows:
function onEdit(event) {
// Move rows to different sheets based on action selected
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = SpreadsheetApp.getActiveRange();
if(range.getColumn() == 5 && range.getValue() == "Active") {
var row = range.getRow();
var numColumns = sheet.getLastColumn();
var targetSheet = spreadsheet.getSheetByName("Active");
var target = targetSheet.getRange(targetSheet.getLastRowSpecial(8) + 1, 1);
sheet.getRange(row, 1, 1, numColumns).moveTo(target);
sheet.deleteRow(row);
and I found the below and other examples when googling but nothing seems to work:
function getLastRowSpecial(column){
// Get the last row with data for the whole sheet.
var numRows = targetSheet.getLastRow();
// Get all data for the given column
var data = targetSheet.getRange(1, column, numRows).getValues();
// Iterate backwards and find first non empty cell
for(var i = data.length - 1 ; i >= 0 ; i--){
if (data[i][0] != null && data[i][0] != ""){
return i + 1;
}
}
}
Any help is greatly appreciated!
function onEdit(e) {
//Logger.log(JSON.stringify(e));
//e.source.toast(e.range.columnStart, e.value)
var sh=e.range.getSheet();
if(e.range.columnStart==8 && e.value=="Active") {
var tsh=e.source.getSheetByName("Active");
var target=tsh.getRange(getColumnHeight(8,tsh,e.source) + 1, 1);
sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).moveTo(target);
sh.deleteRow(e.range.rowStart);
}
}
function getColumnHeight(col,sh,ss){
var ss=ss||SpreadsheetApp.getActive();
var sh=sh||ss.getActiveSheet();
var col=col||sh.getActiveCell().getColumn();
var s=0;
var h=0;
if(sh.getLastRow()) {
var v=sh.getRange(1,col,sh.getLastRow(),1).getValues().map(function(r){return r[0];});
v.forEach(function(e,i){if(e==''){s++;}else{s=0;}h++;});
}
return (h-s);
}

Google sheets script - Strip extra data from a second line and add it to the first line

I'm trying to build a script in google sheets script that will iterate down a sheet, row by row, and if it encounters an ID number in the first cell of that row that is identical to the one above it, it strips out the data from every cell EXCEPT Column A and B, and appends it to the line above. Ideally, this would work with an indeterminate number of duplicate row IDs, might be 2, might be 3, might be 4.
After stripping out the data I want to keep (eg colums C and onward), I then want to delete the entire contents of the processed duplicate ID row, but I just haven't put that in my script until after it copies the data correctly.
In this example, sheet rows 6, 7 and 8 have identical ID numbers (Column A)
Here is the result I'm trying to get:
And here is the result I'm getting:
I've tried a number of different ways, and torn down and rebuilt my script a couple of times without getting the result I want:
function stripMiner() {
var ss = SpreadsheetApp.openById("1WDPoTICQvdruxfhAwHLtA51fz05DqyZ-NhNfpAyPO6Y");
var mainSheet = ss.getSheetByName("Main");
var startRow = 5;
var numRows = mainSheet.getLastRow();//obtains the last row in the sheet
var setrgh = mainSheet
var dataRange = mainSheet.getRange(startRow, 1,4,120); //rowStart, columnStart, row count, column count, the columncount needs to be large enough to encompass all your ancillary data
var data = dataRange.getValues();
var iter = 0;
var maxItRow = 4;
var prevIdNum = 0;
var dupCount = 1;
var cc1 = "P5"; //Cells to dump check values into
var cc2 = "P6";
var dumpRow = startRow;
//if (numRows >= maxItRow){var maxIter = maxItRow;}
for (i in data){
if (iter != maxItRow){ //making sure we haven't gone over the iteration limit
var row = data[i];
var idNum = (row[0]);
var jCount = 0; //resets icount if the id number is different icount is used to skip some cells in a row
if (idNum == prevIdNum){//only proceed if we've hit another line with the same ID number
dupCount = +1; //increment the dupcount value
mainSheet.getRange(cc2).setValue("dupCount"+dupCount); //dupcount check value
var rowIterStart = 5; //RowIterStart is used to add to rowiter, EG if your data is 20 columns wide, and you start transposing from column 4, then this will want to be about 17
var rowIter = 1;
for (j in row){
if (jCount >= 2){ //the integer here is the column where it will begin to transpose data
mainSheet.getRange(dumpRow-1,(rowIterStart*dupCount)+(rowIter)).setValue(row[j]); //startRow+(iter-dupCount)
mainSheet.getRange(cc1).setValue("dumprow"+dumpRow);
}
rowIter+=1;
jCount +=1;
}
}
else{
var dupCount = 1;
dumpRow +=1;
}
prevIdNum = (row[0]); //sets the most recently processed rows ID number
}
iter +=1;
}
}
I'm not quite sure where I'm going wrong. Does anyone have any suggestions? Thanks!
(Also I'm still just a beginner with this so if I've overlooked anything obvious or taken the wrong approach to do this, I apologize!)
The results for the questioner's code in the case of copied data arise from a convoluted loop. In essence, though duplicates were identified, there was a mis-counting to assign the copied data to the correct rowID. So far as clearing data, no provision was included.
The following code works to meet the questioner's goals, though it is far from perfect.
At present, the recalculation of the "last column" after copy each duplicate is an absolute rather than than a row-based figure. So, if a duplicate was detected for, say, ID=3, the data would be copied to column 12 rather than column 6. This requires the addition of a simple dupID row counter.
The second factor is the calculation of the last column in the spreadsheet.
var dataRange = mainSheet.getRange(startRow, 1,Rowlast+1,120);
The questioner used 120 columns; and I have retained that number simply for the sake of consistency. The questioner should re-assess whether this is excessive.
function ejb_so_5284922701() {
var ss = SpreadsheetApp.openById("<< insert questioners spreadsheet ID>>");
var mainSheet = ss.getSheetByName("<< insert questioner's sheet name >>");
var startRow = 5;
// calculate the last row containing data
var Rowvals = ss.getRange("A5:A").getValues();
var Rowlast = Rowvals.filter(String).length; //6
Logger.log("last row = " + Rowlast); // DEBUG
// calculate the last column containing data
var cell = mainSheet.getRange("A5"); //or however you determine "cell"
var drCol = mainSheet.getDataRange().getLastColumn();
Logger.log('getLastColumn = ' + drCol); //DEBUG
for (var i = drCol; i >= 1; i--) {
if (mainSheet.getRange(cell.getRow(), i).getValue() != "") {
break;
}
}
var lastColumn = i;
Logger.log("Last column with data = " + lastColumn); //DEBUG
var setrgh = mainSheet
// numColumns neds to be reviewed
var dataRange = mainSheet.getRange(startRow, 1, Rowlast + 1, 120); //rowStart, columnStart, row count, column count, the column count needs to be large enough to encompass all your ancillary data
// start row = 5, 1= column A, 4, rows, 120, columns
Logger.log("startRow = " + startRow + ", and the datarange = " + dataRange.getA1Notation()); //DEBUG
var data = dataRange.getValues();
Logger.log("length of data =" + data.length); //DEBUG
var lastid = 0;
for (i = 0; i < data.length; i++) {
if (i == 0) {
// if this is the first row, then assign anything but zero to last id
lastid = 100;
Logger.log(" this is the first row; set last id to 100");
}
var thisid = data[i][0];
// evaluate whether this is a duplicate ID
if (thisid == lastid) {
// this is a dup
Logger.log("i=" + i + ". This is a dup" + ", name is " + data[i][2]); //DEBUG
var stufftocopyrange = mainSheet.getRange(startRow + i, 3, 1, 3);
var stufftocopy = stufftocopyrange.getValues();
Logger.log("the range to copy is " + stufftocopyrange.getA1Notation()); //DEBUG
var targetrange = mainSheet.getRange(startRow + lastid - 1, lastColumn + 1, 1, 3);
targetrange.setValues(stufftocopy);
lastColumn = lastColumn + 3;
var duprange = mainSheet.getRange(startRow + i, 1, 1, 5);
Logger.log("the range to clear is " + duprange.getA1Notation()); //DEBUG
duprange.clearContent();
} else {
// no dup
//assign lastid value
var lastid = thisid;
Logger.log("i=" + i + ". No dup. Last id set to " + lastid); // DEBUG
} // if
} // end for loop
}
BEFORE
AFTER
The solutions previously posted didn't quite get the result I needed, however I managed to cobble together something that works for my purposes. It expects to see data in the format like:
And turn it into something like this:
Where it uses duplicate ID numbers (with an indeterminite number of duplicates) to pull only certain columns of data from the duplicate lines and append it to the first line.
function stripMiner() {
var ss = SpreadsheetApp.openById("1WDPoTICQvdruxfhAwHLtA51fz05DqyZ-NhNfpAyPO6Y");
var mainSheet = ss.getSheetByName("Main");
var startRow = 5;
var numRows = mainSheet.getLastRow();//obtains the last row in the sheet
var setrgh = mainSheet
var dataRange = mainSheet.getRange(startRow, 1,3,120); //rowStart, columnStart, row count, column count, the columncount needs to be large enough to encompass all your ancillary data
var data = dataRange.getValues();
var iter = 0;
var maxItRow = 4;
var prevIdNum = 0;
var dupCount = 1;
var cc1 = "P5"; //Cells to dump check values into
var cc2 = "P6";
var dumpRow = startRow;
//if (numRows >= maxItRow){var maxIter = maxItRow;}
for (i in data){
if (iter != maxItRow){ //making sure we haven't gone over the iteration limit
var row = data[i];
var idNum = (row[0]);
var jCount = 0; //resets icount if the id number is different icount is used to skip some cells in a row
if (idNum == prevIdNum){//only proceed if we've hit another line with the same ID number
dupCount = +1; //increment the dupcount value
mainSheet.getRange(cc2).setValue("dupCount"+dupCount); //dupcount check value
var rowIterStart = 5; //RowIterStart is used to add to rowiter, EG if your data is 20 columns wide, and you start transposing from column 4, then this will want to be about 17
var rowIter = 1;
for (j in row){
if (jCount >= 2){ //the integer here is the column where it will begin to transpose data
mainSheet.getRange(dumpRow-2,(rowIterStart*dupCount)+(rowIter)).setValue(row[j]); //startRow+(iter-dupCount)
mainSheet.getRange(cc1).setValue("dumprow"+dumpRow);
}
rowIter+=1;
jCount +=1;
}
}
else{
var dupCount = 1;
dumpRow +=1;
}
prevIdNum = (row[0]); //sets the most recently processed rows ID number
}
iter +=1;
}
}
Hopefully someone else who wants to do a similar thing can make use of this too.

Google Apps Script: Email Reminder Have Time Pulled from Column Values

I'm trying to create a Google Sheets setup with a script that will create calendar events and set reminder times for those events.
I have everything working with one exception: I want to be able to have a column "Reminder Time" where each cell in that column can have a different reminder time to correspond to a given calendar entry (So for example, for Type A, a calendar event will be created with a reminder 3 days in advance, but for Type B, the calendar event will be created 6 days in advance, so in the "Reminder Time" column, the first cell would be 4320 and the second cell would be 8640 (times in minutes)).
My problem is that Type A and Type B are both being assigned the first value in the "Reminder Time" column (The reminder time column is column 8).
function pushToCalendar() {
var sheet = SpreadsheetApp.getActiveSheet();
//Define reminder, THIS IS WHERE MY PROBLEM SEEMS TO LIE
var reminder = sheet.getRange(2, 8).getValue();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(2, 1, lastRow, 5);
var values = range.getValues();
var calendar = CalendarApp.getCalendarById('joncodle9gk4#group.calendar.google.com')
var numValues = 0;
for (var i = 0; i < values.length; i++) {
if ((values[i][0].length > 0) && (values[i][3].length > 0)) {
if (values[i][4] != 'y') {
var newEventTitle = 'Note Due: ' + values[i][0] + ' - ' + values[i][3];
var newEvent = calendar.createAllDayEvent(newEventTitle, values[i][2]);
newEvent.addEmailReminder(reminder);
var newEventId = newEvent.getId();
sheet.getRange(i + 2, 5).setValue('y');
sheet.getRange(i + 2, 6).setValue(newEventId);
}
}
numValues++;
}
}
Your "reminder" is only ever read from H2, rather the row for that event. So you need to move your
var reminder = sheet.getRange(2,8).getValue();
into your row loop:
var reminder = sheet.getRange(i+1,8).getValue();

Average of Numbers in Column If Condition In Different Column Satisfied

I have a data range of which the first column are dates and the second column are numerical values. There are some rows where the second column value is blank and I do not want them to be counted.
I am trying to find the average of the second column values if they satisfy the criteria of being within 3 months ago from today (blank values should not be counted).
But I am stuck as I cannot even get the total correct. And I do not know how to proceed further to get the average.
The code belows seem to give me appended strings instead of summing up the numbers mathematically.
Can anyone help please?
Thanks in advance.
function average() {
// open spreadsheet
var spreadsheet = SpreadsheetApp.openById("spreadsheetID");
// set the named sheet as active
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheetByName("SheetName"));
// figure out what the last row is
var lastRow = spreadsheet.getSheetByName("Form responses 1").getLastRow();
// the rows are indexed starting at 1, and the first row is the headers, so start with row 2
var startRow = 2;
// get the data range
var responsesValues = spreadsheet.getSheetByName("Form responses 1").getRange("A1:Q" + lastRow).getValues();
// define the dates
var timeToday = new Date().getTime();
var dateToday = new Date().getDate();
var date = new Date();
var threeMonthsAgo = new Date(date.getFullYear(), date.getMonth() - 3, 0);
// grab column 1 (date of entry column) (second variable in getRange function below)
var dataRange = sheet.getRange(2,1,lastRow-startRow+1,1 );
var numRows = dataRange.getNumRows();
var dateOfEntryValues = dataRange.getValues();
// grab column 2 (values to be averaged)
range = sheet.getRange(2, 2, lastRow-startRow+1, 1);
var Values = range.getValues();
var warning_count = 0;
var sumValues = 0;
// Loop over the values
for (var i = 0; i <= numRows - 1; i++) {
var dateOfEntry = dateOfEntryValues[i][0];
if(dateOfEntry > threeMonthsAgo && dateOfEntry !== "") {
// if it's within 3 months ago, add the values.
sumValues += Values[i][0];
warning_count++;
}
}
There is a lot more simple version. Just put into the Destination Cell the formula
=ArrayFormula(AVERAGE(IF(DATEDIF(C1:C;TODAY();"D")<=90;E1:E)))
replacing C1:C with the column with dates and E1:E with the column with numbers.

Categories

Resources