Simple Google Spreadsheet Script to copy and paste cells gives error - google-apps-script

I have some relatively simple code below that throws an error: : "Cannot convert NaN to (class)".
All I want to do is copy some cells from one place to another!
Can anyone please let me know what is wrong with this code?
Many thanks in advance.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet 1');
var values = sheet.getDataRange().getValues()
for( var row = values.length -1; row >= 0; --row )
if (values[row][5] == 'Here')
var maxRows = sheet.getMaxRows()
var startRow = (row)+1
var numRows = (maxRows)-(row)
var Range = sheet.getRange(startRow, 3, numRows, 3).getValues()
sheet.getRange(row, 3, numRows, 3).setValues(Range) // (row, column, numRows, numColumns)
}
So as the code hopefully shows, I want to copy cells in the range C:E but only rows x to getMaxRows(), where x is the row number where 'Here' is found in column F, plus 1. Then I want to paste this into the same columns C:E but one row higher than originally (into the same row as 'Here' in column F).
Any help would be very much appreciated. Thanks for looking.
EDIT: The error is on this line :
var Range = sheet.getRange(startRow, 3, numRows, 3).getValues()
In my sheet, the cells to be copied could countain blank cells and even entire blank rows. Could this be causing the issue?

Starting from your description instead of your code (why don't you use {} in loops and conditions ?) I suggest you try this
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet 1');
var values = sheet.getDataRange().getValues()
var maxRows = sheet.getLastRow()
var datatoCopy = []
for( var row = values.length -1; row >= 0; --row ){
if (values[row][5] == 'here'){
var whereToCopy = row+1
Logger.log(whereToCopy);
break
}
}
for(row=whereToCopy-1;row<maxRows;++row){
datatoCopy.push([values[row][2]+'**']);// store column data in an array - remove the ** that I used to see what was copied ;-)
}
Logger.log(datatoCopy)
sheet.getRange(whereToCopy, 5, datatoCopy.length, 1).setValues(datatoCopy);// overwrite data to column E
}
Following your comment, try this version ?
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet 1');
var values = sheet.getDataRange().getValues()
var maxRows = sheet.getLastRow()
var datatoCopy = []
for( var row = values.length -1; row >= 0; --row ){
if (values[row][5] == 'here'){
var whereToCopy = row
Logger.log(whereToCopy);
break
}
}
for(row=whereToCopy-1;row<maxRows-1;++row){
var rowData=[]
rowData.push(values[row+1][2]+'*C*');// I added these 'indicators' to show what happens... delete them when the result is ok ;-)
rowData.push(values[row+1][3]+'*D*');//
rowData.push(values[row+1][4]+'*E*');//
datatoCopy.push(rowData);// store column data in an array - remove the ** that I used to see what was copied ;-)
}
Logger.log(datatoCopy)
sheet.getRange(whereToCopy, 3, datatoCopy.length, datatoCopy[0].length).setValues(datatoCopy);// overwrite data to column E
}

Related

Google Script to Delete a Range of Empty Rows

I need to use a button to delete empty rows in the range A1:A25 on Sheet1 in google sheets. Column A will be the reference column for empty cells. I cannot seem to find a simple answer on how to define the Range for the deletion of empty rows. I do not want it to delete empty rows below row 25.
// Deletes any row whose first column is blank
// WARNING: Assumes any valid row has data in column 1
function deleteBlankRows() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet1");
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
for (var i = lastRow; i > 0; i--) {
var range = sheet.getRange(i,1);
var data = range.getValue();
if (data == '') {
sheet.deleteRow(i);
}
}
}
I believe your goal is as follows.
You want to delete rows when the column "A" is empty.
As a condition, you don't want to delete rows after row 25.
When your script is modified, how about the following modification?
From:
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
To:
var lastRow = 25;
In this modification, when the column "A" of row 25 is empty, that row is deleted. When you want to leave row 25, please modify it to var lastRow = 26;.
Note:
In your script, when deleteRow() is run in the loop, when the process time is long, how about the folloging modified script? This modified script deletes the rows with the empty of column "A" before the row 25. If you want to leave row 25, please modify var v1 = values.splice(0, 25); to var v1 = values.splice(0, 26);.
function deleteBlankRows() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var values = range.getValues();
var v1 = values.splice(0, 25);
var res = [...v1.filter(([a]) => a.toString() != ""), ...values];
range.clearContent();
sheet.getRange(1, 1, res.length, res[0].length).setValues(res);
}
In this case, when you want to delete rows after row 25, please modify var res = [...v1.filter(([a]) => a.toString() != ""), ...values]; to var res = [...v1, ...values.filter(([a]) => a.toString() != "")];.
Added:
From your following reply,
The change to last row worked great. I, however, now need to stop the deletion at row 11. The code I have deletes all the way to row 1 (I know that is what is in my original post). Things changed in the last few minutes.
When you want to delete rows from 11 to 25 and when you want to modify your script, how about the following modification?
From:
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
for (var i = lastRow; i > 0; i--) {
To:
var lastRow = 25;
for (var i = lastRow; i >= 11; i--) {
In this modification, the rows are deleted from 11 to 25.
If you will use my 2nd modification, how about the following modification?
function deleteBlankRows() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet1");
var range = sheet.getDataRange();
var values = range.getValues();
var v2 = values.splice(0, 25);
var v1 = v2.splice(0, 10);
var res = [...v1, ...v2.filter(([a]) => a.toString() != ""), ...values];
range.clearContent();
sheet.getRange(1, 1, res.length, res[0].length).setValues(res);
}

Insert row below based on the column value using google scripts

I stuck with this. Seems easy at first then I got lost!
What I am aiming for:
to insert a row(s) based on a cell value (Col C) -- insert row(s) below
after a new row(s) is inserted, copy the data from previous row + string value from previous row, removing that value as well from the previous row.
I am trying to use this formula but I got lost...
function addRows(){
var ss = SpreadsheetApp.getActive();
var sheet1 = ss.getSheets()[1];
var dataRange = sheet1.getDataRange();
var dataValues = dataRange.getValues();
for (var i = 0; i<dataValues.length; i++) {
for (var j= 0; j<dataValues.length; i++) {
/* If blank or 0 - zero, skip */
if (dataValues[i][3] == "" || dataValues[i][3]== 0) {continue;}
/* If value is >=1, insert new row(s) below the active row */
if (dataValues[i][3] >=1) {
sheet1.insertRowAfter(i);
sheet1.getRange(...) // copy the data from previous + string
}
}
}
}
In your shared Spreadsheet, you want to convert from the values of "A2:B3" to the values of "A6:B11".
In your shared image, you want to achieve as follows.
From
Data1 A0HD, B0DP
Data2 C12X, D0B1, E2C1, F6H1
To
Data1 A0HD
Data1 B0DP
Data2 C12X
Data2 D0B1
Data2 E2C1
Data2 F6H1
You want to achieve this using Google Apps Script.
I could understand like above. If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Sample script:
In order to test the script, please use the following script to your shared Spreadsheet. And run the script.
function myFunction() {
var sourceSheetName = "Sheet6";
var destinationSheetName = "Sheet6";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sourceSheetName);
// Retrieve source values.
var values = sheet.getRange("A2:B3").getValues();
// Convert values.
var result = values.reduce(function(ar, [a, b]) {
var temp = b.split(",");
for (var i = 0; i < temp.length; i++) {
ar.push([a, temp[i].trim()]);
}
return ar;
}, []);
// Put values.
var dstSheet = ss.getSheetByName(destinationSheetName);
dstSheet.getRange(dstSheet.getLastRow() + 1, 1, result.length, result[0].length).setValues(result);
}
In above script, when the script is run, the values of "A2:B3" from the "Sheet6" are retrieved and the converted values are put to the last row of "Sheet6".
If only input values are put to the source sheet, you can also use var values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2).getValues(); instead of var values = sheet.getRange("A2:B3").getValues();.
Note:
This is a simple sample script. So please modify this for your actual Spreadsheet.
Reference:
reduce()

google script note row colorGoogle Script row color detector

I need a little assistance writing a google script function.
I have a Google Sheet where each individual row may have a different color assigned to it.
I need a script that can note the html color code found in the first cell of each row, in that cell.
So for example lets say row 1 is a green row and row 2 is a blue row, the cell A1 should say #00ff00 and A2 should say #0000ff after running the script. Below is what I have so far.
function colorDetector() {
var startRow = 1; // First row of data to process
var numRows = 3; // Number of rows to process
var currentsheet = 'Production' // What sheet you would like to process (must be within ' ')
//This section prepares the document to be read
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName(currentsheet);
var dataRange = sheet.getRange(startRow, 1, numRows, 15) // Fetch values for each row in the Range (row, column, numRows, numColumns)
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
//this is the section i cant seem to get working correctly
var color = row[0].getbackground(); //should set the variable "color" to the html color found in the first cell of the current row.
sheet.getRange(startRow + i, 1).setValue(color); // notes the variable found
SpreadsheetApp.flush();
}
}
Here is what I ended up figuring out on my own. It works perfectly.
function colorDetector2() {
var startRow = 1; // First row of data to process
var currentsheet = 'sheet 4' // What sheet you would like to process (must be within ' ')
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName(currentsheet);
var numRows = 10; // Number of rows to process
var dataRange = sheet.getRange(startRow, 1, numRows, 20) // Fetch values for each row in the Range (row, column, numRows, numColumns)
var colordata = dataRange.getBackgrounds();
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var color = colordata[i]
var colorconv = String(color[1]);
if (colorconv == "#00ff00") {
//do something here;
} else {
//do something else here;
}
}
}

google scripts move row to another spreadsheet reordering the columns

I have a 4 sheet Google Sheets workbook. The 'Main' sheet has the data dumped into the first 11 columns automatically. The 12th column is set up as the manual move trigger, to move selected column data, on a per row basis, to the target sheet (one of the other 3 sheets). I'm able to move only the columns that I want to the selected target sheet, for example columns 1, 4, 9 and 10. But those are the columns that get populated in the target sheet when the data moves. I want the data to move into the first 4 columns of the target sheet instead. I've searched a number of forums and can't find if this is even possible to do. This is my current script:
function onEdit() {
// base definitions
var ss = SpreadsheetApp.getActiveSpreadsheet();
var editedSheet = ss.getActiveSheet();
var editedCell = editedSheet.getActiveCell();
var wordInCell = editedCell.getValue().toString();
var moveSheet = "Main";
var moveColumn = 12;
var moveWords = ["move5x", "move6x", "moveas"];
var targetSheets = ["5x", "6x", "Assigned"];
// if the edit was mot in the moveSheet jump out
var sheetIndex = moveSheet.indexOf(editedSheet.getName());
if (sheetIndex == -1) return;
// if the edit was mot in the move column jump out
if (editedCell.getColumn() != moveColumn) return;
// if the value in editedCell isn't one of the move words, jump out
var moveWordsIndex = moveWords.indexOf(wordInCell);
if (moveWordsIndex == -1) return;
// if the value in editedCell is one of the move words, set up columnsToMove
var columnsToMove;
if (moveWordsIndex == 0 || moveWordsIndex == 1) {columnsToMove = [[1, 4], [8, 8]]};
if (moveWordsIndex == 2) {columnsToMove = [[1, 1], [4, 4], [9, 10]]};
// copy data from editedSheet's sourceRow to targetSheet's targetRow
var sourceRow = editedCell.getRow();
var targetSheet = ss.getSheetByName(targetSheets[moveWordsIndex]);
var targetRow = targetSheet.getLastRow();
targetSheet.insertRowAfter(targetRow++);
for (var i = 0; i < columnsToMove.length; i++) {
var startColumn = columnsToMove[i][0];
var numColumns = columnsToMove[i][1] - startColumn + 1;
var numCopiedColumns = columnsToMove.length;
var rangeToCopyFrom = editedSheet.getRange(sourceRow, startColumn, 1, numColumns);
var rangeToCopyTo = targetSheet.getRange(targetRow, startColumn, 1, numColumns);
rangeToCopyFrom.copyTo(rangeToCopyTo);
}
}
I have tried manipulating rangeToCopyTo every way I could think of, but the results are never what I'm looking for.
Does anyone know if reordering selected columns is even possible? If it is, am I at least on the right track to doing it?
Thanks
Why don't you set your target range's start column to 1?
var rangeToCopyTo = targetSheet.getRange(targetRow, 1, 1, numColumns);
It should do the trick if I got you right.
You could also go with .getValues() and .setValues() stuff that works in a pretty similar way.
And there's also a way to you use separate .setValue() function:
for (var i = 0; i < columnsToMove.length; i++) {
var startColumn = columnsToMove[i][0];
var numColumns = columnsToMove[i][1] - startColumn + 1;
var numCopiedColumns = columnsToMove.length;
var rangeToCopyFrom = editedSheet.getRange(sourceRow, startColumn, 1, numColumns).getValues();
for (var i = 0; i < numCopiedColumns; i++) {
//here you insert your values into first 'numCopiedColumns' columns in the targetRow
targetSheet.getRange(targetRow, i + 1).setValue(rangeToCopyFrom[i]);
}
}
It will probably be more time-consuming as you are performing .setValue() operation for each separate value instead of pasting the data as a whole but this can give you more freedom in choosing the target columns.

Deleting rows in google sheets using Google Apps Script

I encountered the weirdest error while trying to delete rows that match a specific value using Google Apps Script.
Here is my Code:
function myFunction() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("file.csv");
var values = sheet.getRange("N2:N").getValues();
var row_del = new Array();
for(var i=0;i<values.length;i++)
{
if(values[i] == 'del'){
row_del.push(i+2); // This line was added for debugging purposes.
// sheet.deleteRow(i+2) was the line that was in this condition
// (i+2) is used because row 1 has headers and the range starts from 0.
}
}
// Logger.log(row_del);
// GmailApp.sendEmail("my_email_address", "subject", row_del)
for (var i = 0; i < row_del.length; i++)
{
sheet.deleteRow(row_del[i]);
}
}
The code that I have written picks up the row numbers that should be deleted but not all these rows are deleted in my first try. I should execute my script a number of times for these rows to be deleted.
If my code has an error, it should show up and if the logic is wrong, incorrect rows must be deleted. I encounter neither of these scenarios and I should just execute this function multiple times.
Is there something that I'm missing here?
When a row is deleted from a sheet, the rows below it get renumbered even as the script continues to run. If the script subsequently tries to also delete those rows, the result is unpredictable. For this reason, when deleting rows one should proceed from bottom to top. In your case, like so:
for (var i = row_del.length - 1; i>=0; i--) {
sheet.deleteRow(row_del[i]);
}
refer to lock function suggested by google team:
var lock = LockService.getScriptLock();
lock.waitLock(30000); // lock 30 seconds
//do whatever you want here
lock.releaseLock();
That way, you got your deleting job work once at a time! The system thread won't go on to other jobs until 30 seconds is up or releasing the lock.
google dev-document: https://developers.google.com/apps-script/reference/lock/lock
To delete blank rows from a single named sheet, assuming column 1 has data in valid rows.
Search and delete from highest row number to lowest row number.
// Deletes any row whose first column is blank
// WARNING: Assumes any valid row has data in column 1
function deleteBlankRows() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet3");
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
for (var i = lastRow; i > 0; i--) {
var range = sheet.getRange(i,1);
var data = range.getValue();
if (data == '') {
sheet.deleteRow(i);
}
}
}
increment i only if you didn't delete a row
function del_F_rows(){
var i=1;
while(!sht_balanceHistory.getRange(i,1).isBlank()){
if(sht_balanceHistory.getRange(i,3).getValue()=="F")
sht_balanceHistory.deleteRow(i);
else
i=i+1;
}
}
You can just delete the rows alter the last row using the deleteRows function like this:
var maxRows = newsheet.getMaxRows();
var lastRow = newsheet.getLastRow();
if (maxRows-lastRow != 0)
{
newsheet.deleteRows(lastRow+1, maxRows-lastRow);
}
Update 2020
A faster and a more modern JavaScript approach would be to use forEach and reverse() to iterate backwards.
It makes more sense to flatten the values array since it concerns data of a single column.
Solution:
function myFunction() {
const doc = SpreadsheetApp.getActiveSpreadsheet();
const sheet = doc.getSheetByName("file.csv");
const values = sheet.getRange("N2:N").getValues().flat();
values.reverse().forEach((r,i)=>{
if (r=='del'){
sheet.deleteRow(values.length-i+1);
}
});
}
So, is this what it should look like?
function myFunction() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet1");
var values = sheet.getRange("A:A").getValues();
var row_del = new Array();
for(var i=0;i<values.length;i++)
{
if(values[i] == 'N'){
row_del.push(i+2); // This line was added for debugging purposes.
// sheet.deleteRow(i+2) was the line that was in this condition
// (i+2) is used because row 1 has headers and the range starts from 0.
}
}
// Logger.log(row_del);
// GmailApp.sendEmail("my_email_address", "subject", row_del)
for (var i = row_del.length - 1; i>=0; i--) { sheet.deleteRow(row_del[i]); }
}
Copy pasting from: https://gist.github.com/dDondero/285f8fd557c07e07af0e
Instead of looping through the rows twice, you can count how mwny rows have been deleted, to calculate the correct index for the row that you will delete next.
function deleteRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[0] == 'delete' || row[0] == '') { // This searches all cells in columns A (change to row[1] for columns B and so on) and deletes row if cell is empty or has value 'delete'.
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
This is a script I used to delete everything but certain values, but can be easiliy modified to keep certain strings and runs a lot faster than looping through all the data filters out and deletes the values you want to remove and bulk deletes. my data had about 10000 rows so loop would have taken forever.
function DeleteCertainRows(){
columnCopy("D","Z","YourSheetName");//Copy from your target column in this case D to another column in this case Z
replaceInSheet("Z","YourSheetName","keep String 1","keep");//find and replace the value with what you want to keep
replaceInSheet("Z","YourSheetName","keep String 2","keep");//Can repeat for additional values
DeleteValueInColumn("Z","YourSheet","keep");//filters and deletes all other values Column is case sensitive and cant go past Z
};
function replaceInSheet(repColumn,sheetname, to_replace, replace_with) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var values = sheet.getRange(repColumn+'1:'+repColumn+sheet.getMaxRows());
var textFinder = values.createTextFinder(to_replace);
var replaceall = textFinder.replaceAllWith(replace_with);
};
function columnCopy(copyfrm,copyto,sheetname){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var copyrange = sheet.getRange(copyfrm+'1:'+copyfrm+sheet.getMaxRows());
var pasterange = sheet.getRange(copyto+'1:'+copyto+sheet.getMaxRows());
copyrange.copyTo(pasterange);
};
function DeleteValueInColumn(colStr, sheetname, deleteval){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var filterRange=sheet.getDataRange();
var myFilter = filterRange.createFilter();
var CriteriaBuild = SpreadsheetApp.newFilterCriteria();
var Criteria = CriteriaBuild.whenTextDoesNotContain(deleteval);//change to whenTextContains to delete your string value instead of everything else
var myCriteria = Criteria.build();
var str = colStr;
var myCol = parseInt(str.charCodeAt(0) - 64);
Logger.log(myCol);
myFilter.setColumnFilterCriteria(myCol, myCriteria);
var deleterange=sheet.getRange('2:'+sheet.getLastRow());
sheet.deleteRows(deleterange.getRow(), deleterange.getNumRows());
myFilter.remove();
};
Another method is to sort records and delete all at once.
My following code will sort the data with N/A value and will delete all rows at once.
function DeleteErrorLine2() {
var SS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ORDER SHEET"); //Get Open Lines Sheet
var lastRow = SS.getLastRow();
var range = SS.getRange(2, 1, lastRow-1, 33); //get range
range.sort({column: 2, ascending: false}) // filter data descending
var cell = SS.getRange('B1'); // values stored in cell B
var ct = 1; // starting row
while(cell.offset(ct, 0).getValue() == "#N/A" ) {
ct++;
}
if(ct!=1){
ct = ct - 1 // minus 1 to get the last row
SS.deleteRows(2, ct)
}
range.sort({column: 2, ascending: true}) // filter data again ascending
}