Deleting empty rows within a table - google-apps-script

I am new to Google Apps Script script writing and trying to self teach by starting off making a generic table. So far I have:
made a header;
changed the background;
changed the font;
added data validation;
added thick outside borders as well as other borders
and keeping a maximum of five rows and columns at any one time
My question is:
How do I remove empty rows in a data range? I have tried multiple methods so far but I like to have the top row as a border so don't want to delete that. This is the script I have written so far
function formatScriptLearning1() {
var ScriptLearning = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Script learning 1");
var Header = ScriptLearning.getRange("B2").getDataRegion(SpreadsheetApp.Dimension.COLUMNS);
var LastRow = ScriptLearning.getLastRow();
var MaxRows = ScriptLearning.getMaxRows();
var LastColumn = ScriptLearning.getLastColumn();
var MaxColumns = ScriptLearning.getMaxColumns();
var Range = ScriptLearning.getRange(2, 2, LastRow - 1, LastColumn - 1);
var Outside = Range
var DataVal = ScriptLearning.getRange(3, 7, LastRow - 1, 1);
var validation = SpreadsheetApp.newDataValidation().requireValueInList(['Yes', 'No'], true).build();
var RemainingRows = ScriptLearning.getRange(LastRow + 1, 7, MaxRows);
var RemainingColumns = ScriptLearning.getRange(2, LastColumn + 1, 1, 5);
Range.setBorder(true, true, true, true, true, true, null, SpreadsheetApp.BorderStyle.SOLID);
Outside.setBorder(true, true, true, true, true, null, null, SpreadsheetApp.BorderStyle.SOLID_MEDIUM);
Header.setBorder(true, true, true, true, true, true, true, SpreadsheetApp.BorderStyle.SOLID_MEDIUM);
Header.setFontSize(11).setBackground("#ea9999").setFontWeight("Bold").setFontLine("Underline").setFontFamily("Georgia");
if (MaxRows - LastRow != 0) {
ScriptLearning.deleteRows(LastRow + 1, MaxRows - LastRow);
}
if (MaxColumns - LastColumn != 0) {
ScriptLearning.deleteColumns(LastColumn + 1, MaxColumns - LastColumn);
}
DataVal.setDataValidation(validation);
ScriptLearning.insertRowsAfter(LastRow, 5);
ScriptLearning.insertColumnsAfter(LastColumn, 5);
RemainingRows.clearDataValidations().clearFormat();
RemainingColumns.clear();
Any help would be greatly appreciated!

function removeEmptyRows() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getActiveSheet();
const rg=sh.getDataRange();
const vs=rg.getValues();
let d=0;
vs.forEach(function(r,i){if(r.join('').length==0) {sh.deleteRow(i+1-d++);}});
}

Related

How to find the correct rowId when using a limited range from Google Sheet?

I have the below code that checks through a sheet for the words "CASH". Once it's found, it will copy the row to a new sheet then delete the row.
Prior, my range for the values was set up as so:
var range = ss1.getRange(2, 1, lr, lc);
But now that my sheet has 6000+ rows, this is highly inefficient. I've changed it so that it only looks through a range that is lr-100 so that it doesn't have to dig so deep.
Since making that change, my old code to delete rows ss1.deleteRow(i+2) is no longer valid, because i references the row only within that particular range (i.e., if it's the 90th row out of that 100, i = 90, and that would end up deleting the 90th row in my sheet when it should have been, for example, 6500.
question How do I find the correct row# in this new way my script is setup?
var etfar = ["BOTZ"]
function doCash() {
for (var i = 0; i < etfar.length; i++) {
cashMoney(etfar[i])
}
};
function cashMoney(etf) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ss1 = ss.getSheetByName(etf);
var ss2 = ss.getSheetByName("Cash");
var lr = ss1.getLastRow();
var lc = ss1.getLastColumn();
var range = ss1.getRange(lr-100, 1, lr, lc);
var data = range.getValues();
for (var i = data.length - 1; i >= 0; i--)
{
var check = data[i][4] // ith row, 3rd column
if (check.includes("CASH")) {
var rowUsd = data[i];
// something has to happen here to find the exact row
ss2.appendRow(rowUsd);
//ss1.deleteRow(i+2); <-- this is old code. Prior, in my 'range' I used to start my rows from "2", but that was highly inefficient and I've changed it to "lr-100" so that it begins checking from the last 100 rows instead
}
};
};
From now that my sheet has 6000+ rows, this is highly inefficient., in your situation, in order to reduce the process cost of the script, how about using Sheets API? When Sheets API is used for your situation, all rows might be able to be processed. When Sheets API is used for your script, it becomes as follows.
Modified script:
This script uses Sheets API. So, please enable Sheets API at Advanced Google services. And, please set etfar.
function sample() {
var etfar = ["Sheet1", "Sheet2",,,]; // Please set your sheet names.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dstSheet = ss.getSheetByName("Cash");
var dstSheetId = dstSheet.getSheetId();
var lastRow = dstSheet.getLastRow();
var { copyRows, deleteRows } = etfar.reduce((o, e) => {
var srcSheet = ss.getSheetByName(e);
var srcSheetId = srcSheet.getSheetId();
srcSheet.getRange("E2:E" + srcSheet.getLastRow()).getValues().forEach(([v], i) => {
if (v == "CASH") {
o.copyRows.push({ copyPaste: { source: { sheetId: srcSheetId, startRowIndex: i + 1, endRowIndex: i + 2, startColumnIndex: 0 }, destination: { sheetId: dstSheetId, startRowIndex: lastRow, endRowIndex: lastRow + 1, startColumnIndex: 0 } } });
o.deleteRows.push({ deleteDimension: { range: { sheetId: srcSheetId, dimension: "ROWS", startIndex: i + 1, endIndex: i + 2 } } });
lastRow++;
}
});
return o;
}, { copyRows: [], deleteRows: [] });
var requests = [...copyRows, ...deleteRows.reverse()];
if (requests.length == 0) return;
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
When this script is run, the values searched by the column "E" are retrieved from the sheets of etfar, and those values are appended to the destination sheet "Cash". And also, the copied rows of each sheet are removed.
In this script, these processes can be run by one API call.
Note:
When I saw your script, from var check = data[i][4] // ith row, 3rd column, I thought that data[i][4] is the coumn "E". But you say 3rd column. It's the column "C". If you want to search the value of "CASH" from the column "C", please modify getRange("E2:E" + srcSheet.getLastRow()) to getRange("C2:C" + srcSheet.getLastRow()).
References:
Method: spreadsheets.batchUpdate
CopyPasteRequest
DeleteDimensionRequest
I think this is what you want
function cashMoney(etf) {
var ss = SpreadsheetApp.getActive();
var sh1 = ss.getSheetByName(etf);
var sh2 = ss.getSheetByName("Cash");
var lr = sh1.getLastRow();
var lc = sh1.getLastColumn();
var range = sh1.getRange(lr - 100, 1, 101, lc);
var data = range.getValues();
var d = 0;
for (var i = data.length - 1; i >= 0; i--) {
var check = data[i][4] // ith row, 3rd column
if (check.includes("CASH")) {
var rowUsd = data[i];
sh2.appendRow(rowUsd);
sh.deleteRow(lr - 100 + i - d++);The row is data start row + i
}
};
}
The d keeps track of the rows that have already been deleted from the spreadsheet which is necessary because they have not been deleted from the data set.

Conditional borders troubleshooting (Google sheets)

I have a dataset with four different treatment types in blocks of 5 or 7 rows. I want to add borders between each treatment set, so I tried modifying Tedinoz's code. The problem is that this code only adds a border to unique values, and I want to add a border whenever the value is different from the previous row. How can I modify the if statement if (Treatments.indexOf(row) == -1) to search instead for rows whose value for 'Treatment' is different from the previous row?
Here is the full code:
function lineBetween() {
//setup spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
// get key variables
var LastRow = 417
var LastColumn = sheet.getLastColumn();
var NumColumns = 8;
// allow for headers
var headerRows = 1;
//erase any current formatting
var ClearRange = sheet.getRange(1, 1, LastRow, NumColumns).setBorder(false, false, false, false, false, false); // clear all formatting
// get the data
var data = sheet.getRange((+1 + headerRows), 1, (LastRow - headerRows), LastColumn).getValues();
// setup new array
var Treatments = new Array();
// Loop through treatments(Column C)
for (var i in data) {
var row = data[i][2].toString();
// Logger.log("Inside LOOP: i = "+i+", value = "+ row);// DEBUG
// search for unqiue values
if (Treatments.indexOf(row) == -1) { // if value =-1, then the variable is unique
// Logger.log("Inside IF#1: i = "+i+", "+row+" is not referenced. Adding it");//DEBUG
// underline the previous row
if (i != 0) {
// This IF statement to avoid underlining the Header row
var range = sheet.getRange((+i + 1 + headerRows), 1, 1, NumColumns).setBorder(true, false, false, false, false, false, "black", SpreadsheetApp.BorderStyle.SOLID); // format if true
}
// continue to build array
Treatments.push(row);
}
}
// underline the last row of the treatments column
var range = sheet.getRange(LastRow, 1, 1, NumColumns).setBorder(null, null, true, null, false, false, "black", SpreadsheetApp.BorderStyle.SOLID_MEDIUM); // format if true
//Logger.log(Treatments);// DEBUG
}
[1]: https://stackoverflow.com/questions/53053492/conditional-borders-in-google-sheets
Try this:
function lineBetween() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getSheetByName("Sheet1");
sh.getDataRange().setBorder(false, false, false, false, false, false); // clear all formatting
let sr = 2;//data start row
const data = sh.getRange(sr, 1, sh.getLastRow() - sr + 1, sh.getLastColumn()).getValues();
data.forEach((r, i, arr) => {
if (i > 0) {
if (r[2] != arr[i - 1][2]) {
sh.getRange(i + sr, 1, 1, sh.getLastColumn()).setBorder(true, true, true, true, false, false);
}
}
});
}
Actually I think this works better for me:
function lineBetween() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getSheetByName("Sheet0");
sh.getDataRange().setBorder(false, false, false, false, false, false); // clear all formatting
let sr = 2;//data start row
const data = sh.getRange(sr, 1, sh.getLastRow() - sr + 1, sh.getLastColumn()).getValues();
let uA = [];
let dA = [];
data.forEach((r, i, arr) => {
if (i > 0) {
if (r[2] != arr[i - 1][2]) {
sh.getRange(i + sr, 1, 1, sh.getLastColumn()).setBorder(true, false, false, false, false, false);
}
}
});
}

Conditional formatting of borders in Google Sheets using Apps Script

I would like to add borders to cells in a Google Sheet using conditional formatting. I am aware that you cannot do this using the standard conditional formatting process in Google Sheets so I'm trying to get to grips with how to do it using a script.
I have copied a script from the following solution, and attempted to edit it for my needs: (Add border format to row if condition met in Google Sheets)
However, I am still coming to terms with how these scripts work and haven't yet been able to make this work as desired.
The desired effect is that for all rows 5 and higher, where A is not null, a border should be applied to all cells in columns A to M. The sheet is called 'Kit check list', and the script should be triggered any timean edit is made to the sheet.
Here is the my attempt so far
function onEdit() {
GroupMyData(); // trigger this function when edits are made
}
function GroupMyData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Kit check list'); // apply to sheet name only
var rows = sheet.getRange('A5:M'); // range to apply formatting to
var numRows = rows.getNumRows(); // no. of rows in the range named above
var values = rows.getValues(); // array of values in the range named above
var testvalues = sheet.getRange('a5:a').getValues(); // array of values to be tested (1st column of the range named above)
rows.setBorder(false, false, false, false, false, false, "black", SpreadsheetApp.BorderStyle.SOLID); // remove existing borders before applying rule below
//Logger.log(numRows);
for (var i = 0; i <= numRows - 1; i++) {
var n = i + 1;
//Logger.log(n);
//Logger.log(testvalues[i] > 0);
//Logger.log(testvalues[i]);
if (testvalues[i] > 0) { // test applied to array of values
sheet.getRange('a' + n + ':m' + n).setBorder(true, true, true, true, true, true, "black", SpreadsheetApp.BorderStyle.SOLID); // format if true
}
}
};
Unfortunately all it only resets the borders in the specified area, and does not apply a borders to the desired rows.
Any help with this would be much appreciated.
Try this:
function onEdit(e) {
const sh = e.range.getSheet();
if (sh.getName() == 'Kit check list') {
const sr = 5;
const rg = sh.getRange(sr, 1, sh.getLastRow() - sr + 1, sh.getLastColumn());
const vs = rg.getValues();
rg.setBorder(false, false, false, false, false, false, "black", SpreadsheetApp.BorderStyle.SOLID);
const numcolumns = sh.getLastColumn();
vs.forEach((r, i) => {
if (r[0]) {
sh.getRange(i + sr, 1, 1, numcolumns).setBorder(true, true, true, true, true, true, "black", SpreadsheetApp.BorderStyle.SOLID);
}
});
}
}
Demo:
Note: you cannot run this function without providing the event object which populates the e. The only reasonable way to test it is to set it up and save it and edit the sheet.
You might actually like it better this way:
function onEdit(e) {
const sh = e.range.getSheet();
if (sh.getName() == 'Kit check list') {
const sr = 5;
const rg = sh.getRange(sr, 1, sh.getLastRow() - sr + 1, sh.getLastColumn());
const vs = rg.getValues();
//rg.setBorder(false, false, false, false, false, false, "black", SpreadsheetApp.BorderStyle.SOLID);
const numcolumns = sh.getLastColumn();
vs.forEach((r, i) => {
if (r[0]) {
sh.getRange(i + sr, 1, 1, numcolumns).setBorder(true, true, true, true, true, true, "black", SpreadsheetApp.BorderStyle.SOLID);
} else {
sh.getRange(i + sr, 1, 1, numcolumns).setBorder(false, false, false, false, false, false, "black", SpreadsheetApp.BorderStyle.SOLID);
}
});
}
}
Your script works fine. I just fixed one line.
Instead of this:
var n = i + 1;
You need:
var n = i + 5;
Here is the code:
function onEdit() {
GroupMyData(); // trigger this function when edits are made
}
function GroupMyData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Kit check list'); // apply to sheet name only
var rows = sheet.getRange('A5:M'); // range to apply formatting to
var numRows = rows.getNumRows(); // no. of rows in the range named above
var values = rows.getValues(); // array of values in the range named above
var testvalues = sheet.getRange('A5:A').getValues(); // array of values to be tested (1st column of the range named above)
rows.setBorder(false, false, false, false, false, false, "black", SpreadsheetApp.BorderStyle.SOLID); // remove existing borders before applying rule below
for (var i=0; i <= numRows-1; i++) {
var n = i + 5;
//Logger.log(n);
//Logger.log(testvalues[i] > 0);
//Logger.log(testvalues[i]);
if (testvalues[i] > 0) { // test applied to array of values
sheet.getRange('A' + n + ':M' + n).setBorder(true, true, true, true, true, true, "black", SpreadsheetApp.BorderStyle.SOLID); // format if true
}
}
};
without any script, you can do it by first using conditional formatting in MS Excel, then importing the workbook into google sheets ! weird ...

Define whole column in API range specification

I can use below syntax to refer whole column A,B and C:
A1:C
Below script will refer to full available cells!
var myRange = {
'sheetId': sheet.getSheetId(),
'startRowIndex': 0,
'endRowIndex': sheet.getLastRow(),
'startColumnIndex': 0,
'endColumnIndex': sheet.getLastColumn()
}
If user insert a new line, this range will not cover it. How to change it to support whole column just like A1:C?
Full script as below:
function addConditonalFormat() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getActiveSheet()
sheet.clearConditionalFormatRules()
var colorMerged = {'red': 222/255, 'green': 235/255, 'blue': 246/255, 'alpha': 0.7}
var colorSkipped = {'red': 222/255, 'green': 235/255, 'blue': 0, 'alpha': 0.7}
var myRange = {
'sheetId': sheet.getSheetId(),
'startRowIndex': 0,
'endRowIndex': sheet.getLastRow(),
'startColumnIndex': 0,
'endColumnIndex': sheet.getLastColumn()
}
var config = [["merged",colorMerged],["skipped",colorSkipped]]
var requests = []
for (var i=0;i<config.length;i++) {
var row = config[i]
var keyword = row[0]
var color = row[1]
Logger.log(keyword + ":" + color)
var cond = {'addConditionalFormatRule': {
'index': 0,
'rule': {
'ranges': [ myRange ],
'booleanRule': {
'format': {'backgroundColor': color},
'condition': {
'type': 'CUSTOM_FORMULA',
'values':[{'userEnteredValue': '=$A:$A="' + keyword + '"'}]},},},} }
requests.push(cond)
}
var format_req = {
'requests': requests,
'includeSpreadsheetInResponse': false,
}
Sheets.Spreadsheets.batchUpdate(JSON.stringify(format_req), ss.getId())
}
After run the script, then insert rows after last row, the conditional format will not apply to the new added rows!
Simple: to refer to the entirety of the sheet, do not supply any index specifications. Per the DimensionRange and GridRange documentation, missing indices indicate an unbounded specification.
const theWholeSheet = {
sheetId: sheet.getSheetId()
};
const noFirstRowOrFirstCol = {
sheetId: sheet.getSheetId(),
startColumnIndex: 1,
startRowIndex: 1
};

Undefined error on google spreadsheet script

I am desperately looking for some help about an error I keep getting when running a function on a Google Spreadsheet.
I have written all the code and I get two situations:
if I run the script from the sheet where it is supposed to work, I get the error "Oops
We're sorry. The server encountered an error. Please press "OK" to refresh the sheet" even though all the operations are actually completed below the error popup.
if I run the script from another sheet the script is completed successfully.
I thought this problem could have been related to the active sheet function, so I started debugging to find a solution. I noticed I get absolutely no problems with all of the code except a part in which I ask the spreadsheet to make some operations on the columns: first to create a new one, then to resize two of them and eventually to delete one.
I am attaching all the code so that you can get a better idea of what the script is doing.
Thank you for your attention.
Tony
function forecastSettimanale() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var data = sheet.getDataRange();
// I need to operate on a column that has TOT written on its top, and its
// position can vary
var k = 1;
while (k <= data.getNumColumns()) {
var startRange = sheet.getRange(1, k, 1);
var value = startRange.getValue();
if(value == 'TOT') {
var flagColumn = startRange.getColumn(); //
}
k++;
}
sheet.insertColumnsBefore(flagColumn, 1);
sheet.setColumnWidth(flagColumn - 1, 40);
sheet.setColumnWidth(flagColumn, 50);
sheet.deleteColumn(flagColumn - 6);
sheet.setColumnWidth(flagColumn, 100);
}
NEW edited code with your suggestion:
function forecastSettimanale() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var data = sheet.getDataRange();
var searchElement = 'TOT';
var firstRow = 1, firstColumn = 1, rows = 1;
var lastColumn = sheet.getLastColumn();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(firstRow, firstColumn, rows, lastColumn).getValues();
var flagColumn = range[0].lastIndexOf(searchElement) + 1;
sheet.setColumnWidth(flagColumn - 1, 40);
sheet.deleteColumn(flagColumn - 6);
sheet.insertColumns(flagColumn - 1);
var cellLeggiData = sheet.getRange(2, 3, 1);
var cellData = sheet.getRange(1, flagColumn - 1, 1);
cellData = cellData.setValue(cellLeggiData.getValue());
var last_row = sheet.getLastRow();
var copiaDa = sheet.getRange(2, flagColumn, last_row - 1);
var copiaA = sheet.getRange(2, flagColumn - 1, last_row - 1);
copiaDa.copyTo(copiaA, {contentsOnly: true});
for (var i = 35; i <= lastRow; i+=33) {
var cellaFormula = sheet.getRange(i, flagColumn, 1);
var rigaPrecedente = i - 1;
var totN = sheet.getRange(rigaPrecedente, flagColumn, 1);
var totFore = sheet.getRange(rigaPrecedente, flagColumn - 1, 1);
var letteraColonna = String.fromCharCode(64 + flagColumn);
var letteraColonnaMenoUno = String.fromCharCode(63 + flagColumn);
cellaFormula.setFormula("=" + letteraColonnaMenoUno + rigaPrecedente + "-" + letteraColonna + rigaPrecedente);
}
sheet.setColumnWidth(flagColumn - 1, 100);
sheet.showColumns(flagColumn - 6, 6);
}
I can not reproduce the problem. The code in question works without problem, as you indicate. Maybe you have to do some validations such as, for example, in sheet.deleteColumn(flagColumn - 6); What if flagColumn is less than 6?
I modified a bit the way to get the column that is needed.
/* CODE FOR DEMONSTRATION PURPOSES */
function forecastSettimanale() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var searchElement = 'TOT';
var firstRow = 1, firstColumn = 1, rows = 1;
var lastColumn = sheet.getLastColumn();
var range = sheet.getRange(firstRow, firstColumn, rows, lastColumn).getValues();
var flagColumn = range[0].lastIndexOf(searchElement);
if (flagColumn > -1) {
++flagColumn;
sheet.insertColumnsBefore(flagColumn, 1);
sheet.setColumnWidth(flagColumn - 1, 40);
sheet.setColumnWidth(flagColumn, 50);
sheet.deleteColumn(flagColumn - 6);
sheet.setColumnWidth(flagColumn, 100);
}
}
Try to debug line by line to determine the method generates the error, that way it will be easier to find a solution.