Clear content insted of deleting row - google-apps-script

I have a range with data where I need a script to look for duplicates in the first column, and clear contents of the hole row in that range.
From this
To this
I've found this script, but it deletes the whole row. I need it to only clear content.
function ClearDuplicates() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("03");
var rows = sheet.getLastRow();
var firstColumn = sheet.getRange("A:A").getValues();
for (var i = rows; i >= 2; i--) {
if (firstColumn[i-1][0] == firstColumn[i-2][0]) {
sheet.deleteRow(i);
}
}
}

You need to retrieve the array data from column A and iterate through it comparing with the same array. When a match happens, apply clearContent() function [1] to clear the contents on that range (First get the range you want to clear in that row).
function ClearDuplicates() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var rows = sheet.getLastRow();
var firstColumnValues = sheet.getRange("A1:A" + rows).getValues();
for (var i = 0; i < firstColumnValues.length; i++) {
var value = firstColumnValues[i][0];
for (var j = 0; j < firstColumnValues.length; j++) {
var comparisonValue = firstColumnValues[j][0];
if ((value == comparisonValue) && (j>i)) {
//You need to change the number of columns to which you need to clear the contents, in this case is 10
sheet.getRange(j+1, 1, 1, 10).clearContent();
}
}
}
}
[1] https://developers.google.com/apps-script/reference/spreadsheet/range#clearcontent

You can just grab the range and clear it instead as seen below:
sheet.getRange("R"+i+"C").clear();

Related

How to check whether a new value is in a column?

I want to check for new names in a top 30 ranking from an API that refreshes daily, and then append every new name to an other column if it isn't already in there.
I think a for-loop would be the solution. This is what I got so far.
function appendValues(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var top30Names = ss.getRange("A4:A33").getValues();
var eligibleNames = ss.getRange("P4:P300").getValues();
for (i = 0; i < 30; i++){
var searchKey = top30Names[i]; // search if the eligible name is in the top30names
if (isInArray(searchKey, eligibleNames)){
// do nothing
}
else{
getFirstEmptyRow();
ss.getActiveCell().setValue(searchKey);
}
}
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
function getFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSheet(),
values = sheet.getRange("P4:P300") // the range to search for the first blank cell
.getValues(),
row = 0; //start with the first array element in the 2D array retrieved by getValues()
for (row; row < values.length; row++) {
if (!values[row].join("")) break;
}
return sheet.setActiveSelection("P" + (row + 4)).getRow();//.getLastRow() // column between "" and row + starting_row in range
}
This appends the full top 30 each time, but I only need the new values.
I've found a work around using setFormula. If anyone has a more elegant solution, I'd be happy learn.
function appendNewName(){
setFormula();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var newNames = ss.getRangeByName("newEntries").getValues();
for (i = 0; i < 30; i++){
getFirstEmptyRow();
var x = newNames[i][0];
// Logger.log(x); // What does this do???
if (x.length > 1) {
ss.getActiveCell().setValue(newNames[i]);
}
}
}
function setFormula(){
// first run this
clearFormula();
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getRangeByName("newEntries").setFormula("=IF(ISNUMBER(MATCH(A4,AppendNew,0)),\"\",A4)"); // Sets a formula to the range that will show the new daily entries in top 30
}
function clearFormula(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getRangeByName("newEntries").clear();
}
function getFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSheet(),
values = sheet.getRange("appendNew") // the range to search for the first blank cell
.getValues(),
row = 0; //start with the first array element in the 2D array retrieved by getValues()
for (row; row < values.length; row++) {
if (!values[row].join("")) break;
}
return sheet.setActiveSelection("P" + (row + 4)).getRow(); // column between "" and row + starting_row in range
}

Hiding/unhiding named ranges: Improve script process time?

Below is a script I'm using to automate the hiding/unhiding of groups of rows in Google Sheets.
Col B contains drop-down menus that allow the user to toggle hiding or unhiding rows according to named ranges. However, the script is a bit laggy. Here is a sample spreadsheet where you can see for yourself.
I haven't found a more efficient approach than creating four different arrays in Google Apps Script...but any advice on how to speed up this script would be greatly appreciated!
(NB: The script operates with a "From spreadsheet" and "On edit" trigger.)
function hideRows() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("Sheet");
//create array of spreadsheet's named ranges
var namedRanges = ss.getNamedRanges();
var named = [];
for (var i = 0; i < namedRanges.length; i++) {
named.push(namedRanges[i].getName());
}
var arrName = named.sort();
//create array of named range values
var value = [];
for (var i = 0; i < arrName.length; i++) {
value.push(ss.getRangeByName(arrName[i]).getValue());
}
//create array of named range row indexes
var index = [];
for (var i = 0; i < arrName.length; i++) {
index.push(ss.getRangeByName(arrName[i]).getRowIndex());
}
//create array of named range row numbers
var rows = [];
for (var i = 0; i < arrName.length; i++) {
rows.push(ss.getRangeByName(arrName[i]).getNumRows());
}
for (var i = 0; i < arrName.length; i++) {
//If a value in value is equal to "Collapsed" then hide the corresponding named range
if (value[i] == "Collapsed") {
sheet.hideRows(index[i+1],rows[i+1]);
}
//If a value in value is equal to "Expanded" then show the corresponding named range
else if (value[i] == "Expanded") {
sheet.showRows(index[i+1],rows[i+1]);
}
}
}
I didn't test this. I rewrote the code, and consolidated three of the loops into one loop. The code could be consolidated more, depending upon whether you need to further use the data for something else. For example, you are sorting the range names. If it's not really needed to have them in order, then the code could be consolidated further.
function HideRows() {
var arrName,i,index,L,named,namedRanges,
rngByName,rngNumRows,rngRowIndex,rngVal,rows,
sheet,ss,thisRng,thisRngName,value;
ss = SpreadsheetApp.getActive();
sheet = ss.getSheetByName("Sheet");
named = [];
value = [];
index = [];
rows = [];
//create array of spreadsheet's named ranges
namedRanges = ss.getNamedRanges();
L = namedRanges.length;
for (i = 0; i < L; i++) {
named.push(namedRanges[i].getName());
}
arrName = named.sort();
Logger.log(arrName);
//create array of named range values
L = named.length;
for (i = 0; i < L; i++) {
thisRngName = arrName[i];
thisRng = ss.getRangeByName(thisRngName);
rngVal = thisRng.getValue();
rngRowIndex = thisRng.getRowIndex();
rngNumRows = thisRng.getNumRows();
value.push(rngVal);
index.push(rngRowIndex);//create array of named range row indexes
rows.push(rngNumRows);//create array of named range row numbers
//If a value in arrValue is equal to "Collapsed" then hide the corresponding named range
if (rngVal == "Collapsed") {
sheet.hideRows(index[i+1],rows [i+1]);
}
//If a value in arrValue is equal to "Expanded" then show the corresponding named range
else if (rngVal == "Expanded") {
sheet.showRows(index[i+1],rows [i+1]);
}
}
}

Apps Script: Construct range of rows from array of row numbers

I have a list of row numbers in a spreadsheet which I need to change the background colour of. As the spreadsheet is quite large (10+ sheets, each with almost 5000 rows), I am trying to construct a range so I can batch set the background, as doing each row individually was taking over the max time of 6 minutes.
Here's the code I have:
// highlight required rows
var first = -1, last = -1;
for(var j = 0; j < rowNumsToHighlight.length; j++) {
if(first == -1) {
first = rowNumsToHighlight[j];
continue;
}
// if the current row number is one more than the previous, update last to be the current row number
if(rowNumsToHighlight[j] - 1 == rowNumsToHighlight[j - 1]) {
last = rowNumsToHighlight[j];
continue;
}
// otherwise the last row should be the previous one
else {
last = rowNumsToHighlight[j - 1];
}
var numRows = (last - first) + 1;
var range = sheet.getRange(first, 1, numRows, 4);
if(range.getBackground().toUpperCase() != highlightColour.toUpperCase()) {
range.setBackground(highlightColour);
}
first = -1;
last = -1;
}
rowNumsToHighlight is just an array that looks like: [205,270,271,272,278,279]. So, with that as an example, setBackground should be ran on row 205, on rows 270-272, and on 278-279.
I'm fairly sure the solution is simple, but just can't see it. Thanks for any help.
==== Updated Code ====
Based on Serge's code below, I made it more efficient again by reducing the number of getRange() calls made. Time is down from 78 to 54 seconds.
function updateColours(sheet, array, colour){
var columns = sheet.getLastColumn();
var rows = sheet.getLastRow();
var range = sheet.getRange(1, 1, rows, columns);
Logger.log("Resetting highlight on all rows...");
range.setBackground(null);
var backgrounds = range.getBackgrounds();
for(var n = 0; n < backgrounds.length; n++){
var rowIdx = n + 1;
if(array.indexOf(rowIdx) > -1){
for(var c = 0; c < columns; c++){
backgrounds[n][c] = colour;
}
}
}
Logger.log("Highlighting non-translated rows...");
range.setBackgrounds(backgrounds);
}
Maybe this one is faster(?) and built in a way that will make your work easier (function with arguments).
It writes only once to the sheet (or 2 if you clear colors before writing)...
use like below :
function testBG(){
updateColors(0,[7,8,9,18,19,23]);
}
function updateColors(sheetNum,array){
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheets()[sheetNum];
var columns = sh.getMaxColumns();
var range = sh.getRange(1,1,sh.getMaxRows(),columns);
sh.getRange(1,1,sh.getMaxRows(),columns).setBackground(null);// use this if you want to clear all colors before setting them
var backGrounds = range.getBackgrounds();// get all cells BG
for(var n=0;n<backGrounds.length;n++){
var rowIdx = n+1;
if(array.indexOf(rowIdx)>-1){
for(c=0;c<columns;c++){
backGrounds[n][c]="#F00";// if row number is in the array fill in red
}
}
}
sh.getRange(1,1,sh.getMaxRows(),columns).setBackgrounds(backGrounds);//update sheet in one call
}
test sheet in view only, make a copy to test.
This is how I would do it:
function createRanges() {
var rowNumsToHighlight = [5,7,8,9,18,19];
var arrayLength = rowNumsToHighlight.length;
var loopCounter = 0, thisNumberInArray=0, nextNumberInArray=0, crrentNmbrPlusOne=0;
var currentRangeBegin=0, numberOfRowsInRange=1;
currentRangeBegin = rowNumsToHighlight[0];
for(loopCounter=0; loopCounter < arrayLength; loopCounter+=1) {
thisNumberInArray = rowNumsToHighlight[loopCounter];
nextNumberInArray = rowNumsToHighlight[loopCounter+1];
crrentNmbrPlusOne = thisNumberInArray+1;
if (nextNumberInArray===undefined) {
workOnTheRange(currentRangeBegin, numberOfRowsInRange);
return;
};
if (nextNumberInArray!==crrentNmbrPlusOne) {
workOnTheRange(currentRangeBegin, numberOfRowsInRange);
numberOfRowsInRange = 1; //Reset to 1
currentRangeBegin = nextNumberInArray;
} else {
numberOfRowsInRange+=1;
};
};
};
function workOnTheRange(first,numRows) {
var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet11').getRange(first, 1, numRows, 4);
range.setBackground("red");
};
I've tested the code and it works.

Google Apps Script - Find Row number based on a cell value

I'm looking for some assistance on find the row number for a cell that contains a specific value.
The spreadsheet has lots of data across multiple rows & columns. I'm looping over the .getDataRange and can located the cell containing the value I'm looking for. Once found I'd like to know the row that his cell is in, so that I can further grab additional cell values since I know exactly how many rows down and/or columns over the additional information is from the found cell.
Here is a bit of the code for finding the cell containing a specific string.
function findCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == "User") {
row = values[i][j+1];
Logger.log(row);
}
}
}
}
When outputting Logger.log(row) it gives me the values I'm looking for. I want to determine what row each value is in, so I can then go down X number of rows and over X columns to get the contents of other cells.
I don't know much about google apps but it looks like [i] is your row number in this circumstance.
So if you did something like:
function findCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == "User") {
row = values[i][j+1];
Logger.log(row);
Logger.log(i); // This is your row number
}
}
}
}
This method finds the row number in a particular column for a given value:
function rowOf(containingValue, columnToLookInIndex, sheetIndex) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets();
var dataRange = sheet[sheetIndex].getDataRange();
var values = dataRange.getValues();
var outRow;
for (var i = 0; i < values.length; i++)
{
if (values[i][columnToLookInIndex] == containingValue)
{
outRow = i+1;
break;
}
}
return outRow;
}
But if you do it like that, it won't refresh unless you change any of the parameters, that's because a custom function should be deterministic, that is, for the same parameters, it should always give the same result.
So instead, it's better to do it this way:
function rowOf(containingValue, range) {
var outRow = null;
if(range.constructor == Array)
{
for (var i = 0; i < range.length; i++)
{
if(range[i].constructor == Array && range[i].length > 0)
{
if (range[i][0] == containingValue)
{
outRow = i+1;
break;
}
}
}
}
return outRow;
}
In this case, you need to pass the full column range your looking for like so:
rowOf("MyTextToLookFor", 'MySheetToLookIn'!A1:A)
Where you would replace A by the colum of your choice, and MySheetToLookIn by your sheet's name and MyTextToLookFor by the text you are looking for.
This will allow it to refresh on adding rows and removing rows.
Since 2018 this can be done without a loop using flat().
function findRow(searchVal) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var columnCount = sheet.getDataRange().getLastColumn();
var i = data.flat().indexOf(searchVal);
var columnIndex = i % columnCount
var rowIndex = ((i - columnIndex) / columnCount);
Logger.log({columnIndex, rowIndex }); // zero based row and column indexes of searchVal
return i >= 0 ? rowIndex + 1 : "searchVal not found";
}
ES6 gave us a one liner for this (but expanded for full detail).
function findRow() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss. getActiveSheet(); // OR GET DESIRED SHEET WITH .getSheetByName()
const dataRange = sheet.getDataRange();
const values = dataRange.getValues();
const columnIndex = 3 // INDEX OF COLUMN FOR COMPARISON CELL
const matchText = "User"
const index = values.findIndex(row => row[columnIndex] === matchText)
const rowNumber = index + 1
return rowNumber
}
This can be done with the Text Finder:
function findRow(searchValue){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var thisSheet = ss.getActiveSheet();
var tf = thisSheet.createTextFinder(searchValue)
var thisRow = tf.findNext().getRow()
return thisRow
}
Discovered through this post: Google App Scripts find text in spreadsheet and return location index
Implementing: https://developers.google.com/apps-script/reference/spreadsheet/text-finder
To find a row in a Google Sheets sheet based on the value of a specific column (in this case, the invoice_id column) and edit another column (in this case, the send column) in that row, you can use the following script:
// Replace "Sheet1" with the name of your sheet
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
// Replace "A" with the column letter for the invoice_id column
// and "B" with the column letter for the send column
var invoiceIdColumn = "A";
var sendColumn = "B";
// Replace "12345" with the invoice_id you want to search for
var invoiceIdToSearch = "12345";
// Find the row number of the row with the matching invoice_id
var data = sheet.getDataRange().getValues();
var row = data.findIndex(row => row[invoiceIdColumn - 1] == invoiceIdToSearch) + 1;
// If a row with the matching invoice_id was found
if (row) {
// Set the value of the send column in that row to "true"
sheet.getRange(row, sendColumn).setValue(true);
}
This script will search through the entire sheet for a row with an invoice_id value that matches the invoiceIdToSearch variable, and then set the value of the send column in that row to true.
Note that this script assumes that the invoice_id column is the first column in the sheet (column A) and the send column is the second column (column B). If your columns are in different positions, you will need to adjust the invoiceIdColumn and sendColumn variables accordingly.

sorting columns independently at array level

I have a spreadsheet with several columns that I need to sort individually.
I wrote the script below that works but is a bit slow since it handles each column in turn with getValues() and setValues().
I'd like to find a way to do the whole sorting at an array level for more efficiency but I don't know how... any suggestion ?
Here is the relevant part of the code I use now :
...
sh3.getRange(1,1,1,newData[0].length).setFontWeight('bold');// newData is an array corresponding to the whole sheet
for(col=1;col<newData[0].length;++col){
var val = sh3.getRange(2,col,getLastRowInCol(col),1).getValues().sort();// each column have a different height
sh3.getRange(2,col,getLastRowInCol(col),1).setValues(val)
}
}
function getLastRowInCol(col){
var values = sh3.getRange(2,col,sh3.getLastRow(),1).getValues();// skip header and find last non empty row in column
for(n=0;n<values.length;++n){
if(values[n]==''){break}
}
return n
}
Note : I know there is a Library by Romain Vialard that does the job (sorting columns in 2D arrays) but I'm interrested on how to do it 'manually' for personal JS skills improvement ;-) and also I need to sort every column independently without needing to update the sheet for every column.
How about:
function sortColumns() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2;
var startCol = 1;
var dataRange = sheet.getRange(startRow, startCol, sheet.getLastRow() - startRow + 1, sheet.getLastColumn() - startCol + 1);
var data = dataRange.getValues();
// transpose data so each column item will be listed in an single array
// for each column so that it can be sorted with array.sort()
var rowToCol = [];
for (var i = 0; i < data[0].length; i++) {
rowToCol.push([]);
for (var j = 0; j < data.length; j++) {
// replace empty string with undefined as undefined sorts last
rowToCol[i].push(data[j][i]==""?undefined:data[j][i]);
}
rowToCol[i].sort();
// default sort, as above, is alphabetic ascending. For other methods
// search for Javascript array sort functions
}
// transpose sorted items back to their original shape
var result = [];
for (var i = 0; i < rowToCol[0].length; i++) {
result.push([]);
for (var j = 0; j < rowToCol.length; j++) {
result[i].push(rowToCol[j][i]==undefined?"":rowToCol[j][i]);
}
}
dataRange.setValues(result);
};
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Sort Columns",
functionName : "sortColumns"
}];
sheet.addMenu("Script Center Menu", entries);
};