Conditional formatting of borders in Google Sheets using Apps Script - google-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 ...

Related

specific columns of selected row for print

How to set specific columns of selected row for print ? Google Apps Script - Google Sheets
Where I select a row of the google sheet, the data of these columns should be printed by the macro ("A", "B", "J", "S").
I've written this macro to select rows but I don't know how to make more changes required
function print() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
sheet.getRange(spreadsheet.getCurrentCell().getRow(), 1, 1, sheet.getMaxColumns()).activate();
}
I believe your goal is as follows.
When you select a cell, you want to retrieve the values of the specific columns of the row of the selected cell.
In this case, how about the following modification?
Modified script:
function print() {
var selectColumns = ["A", "B", "J", "S"]; // This is from your question.
var sheet = SpreadsheetApp.getActiveSheet();
var currentRow = sheet.getCurrentCell().getRow();
var columnIndex = selectColumns.map(letter => [...letter.toUpperCase()].reduce((c, e, i, a) => (c += (e.charCodeAt(0) - 64) * Math.pow(26, a.length - i - 1)), -1));
var values = sheet.getRange(currentRow, 1, 1, sheet.getMaxColumns()).getValues().map(r => columnIndex.map(c => r[c]))[0].join(",");
console.log(values) // Here, you can also confirm the values in the log.
Browser.msgBox(values); // You can see the values at a dialog of Spreadsheet.
}
When you select a cell and run this script, the values of the specific columns "A", "B", "J", "S" of the row of the selected cell are retrieved.
At columnIndex, the column letters are converted to the column indexes.
From sheet.getRange(spreadsheet.getCurrentCell().getRow(), 1, 1, sheet.getMaxColumns()).activate();, if you want to activate the specific columns, how about the following modification?
function print2() {
var selectColumns = ["A", "B", "J", "S"]; // This is from your question.
var sheet = SpreadsheetApp.getActiveSheet();
var currentRow = sheet.getCurrentCell().getRow();
sheet.getRangeList(selectColumns.map(e => e + currentRow)).activate();
}
Reference:
map()
Added 1:
From your following reply,
I mean when i using function print2() if selectColumns = ["A", "B", "C" ] , Cells A B C are highlighted And that's great. but when i try To Use CTRL + P and And I make the settings related to print the selected cells, I only see cell c in the print output
First, in this case, it is required to select the continuous ranges like "A1:C1". When the cells "A1:C1" and "E1" are selected, only "E1" is shown. And, when the cells are selected using RangeList, the cells are selected for every cell. I thought that this might be the reason for your current issue. So, when you want to use the selected cells using the printer setting, how about the following sample script?
Sample script:
When you use this script, please set startColumn and endColumn. And, please select a cell. And, please run the script. By this, in this sample, the columns "A" to "C" are selected. When you push CTRL + P and do the settings related to print the selected cells, you can see the 3 cells.
function print3() {
var startColumn = "A"; // Please set the start column.
var endColumn = "C"; // Please set the end column.
var sheet = SpreadsheetApp.getActiveSheet();
var currentRow = sheet.getCurrentCell().getRow();
sheet.getRange(`${startColumn}${currentRow}:${endColumn}${currentRow}`).activate();
}
Added 2:
If you want to use the discrete columns and cells, how about the following sample script?
Sample script:
This script uses Sheets API. So, please enable Sheets API at Advanced Google services.
When you use this script, please set selectColumns. In this case, you can set the discrete columns. And, please select a cell, and run the script of print4. By this, only the columns of selectColumns of the selected row are shown. By this, when you push CTRL + P, you can see the showing cells. This is another workaround for achieving your goal.
After your work is finished, when you run showAll, all rows and columns are shown.
function print4() {
var selectColumns = ["A", "B", "J", "S"]; // This is from your question.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var currentRow = sheet.getCurrentCell().getRow();
var columnIndex = selectColumns.map(letter => [...letter.toUpperCase()].reduce((c, e, i, a) => (c += (e.charCodeAt(0) - 64) * Math.pow(26, a.length - i - 1)), -1));
var sheetId = sheet.getSheetId();
var requests = [...Array(sheet.getMaxColumns())].reduce((ar, _, i) => {
if (!columnIndex.includes(i)) {
ar.push({ updateDimensionProperties: { range: { sheetId, startIndex: i, endIndex: i + 1, dimension: "COLUMNS" }, properties: { hiddenByUser: true }, fields: "hiddenByUser" } });
}
return ar;
}, []);
var maxRows = sheet.getMaxRows();
if (currentRow == 1) {
requests.push({ updateDimensionProperties: { range: { sheetId, startIndex: currentRow, endIndex: maxRows, dimension: "ROWS" }, properties: { hiddenByUser: true }, fields: "hiddenByUser" } });
} else if (currentRow == maxRows) {
requests.push({ updateDimensionProperties: { range: { sheetId, startIndex: 0, endIndex: maxRows - 1, dimension: "ROWS" }, properties: { hiddenByUser: true }, fields: "hiddenByUser" } });
} else {
requests.push({ updateDimensionProperties: { range: { sheetId, startIndex: 0, endIndex: currentRow - 1, dimension: "ROWS" }, properties: { hiddenByUser: true }, fields: "hiddenByUser" } });
requests.push({ updateDimensionProperties: { range: { sheetId, startIndex: currentRow, endIndex: maxRows, dimension: "ROWS" }, properties: { hiddenByUser: true }, fields: "hiddenByUser" } });
}
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
function showAll() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
const requests = [{ updateDimensionProperties: { range: { sheetId: sheet.getSheetId(), dimension: "COLUMNS" }, properties: { hiddenByUser: false }, fields: "hiddenByUser" } }, { updateDimensionProperties: { range: { sheetId: sheet.getSheetId(), dimension: "ROWS" }, properties: { hiddenByUser: false }, fields: "hiddenByUser" } }];
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}

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);
}
}
});
}

Deleting empty rows within a table

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++);}});
}

Borders when row has data but no border if empty

I have a sheet that I want to dynamically add borders to filter results. I have it working where it adds the borders, but when i select another filter it keeps the rows with previous data borders. So if data set is smaller it has all empty borders under it. How would I fix this with this script?
function onEdit(ss) {
var classeur = SpreadsheetApp.getActiveSpreadsheet();
var ss = classeur.getActiveSheet();
var range = ss.getRange("A5:a"); // Modified
range.setBorder(false, false, false, false, false, false);
var values = range.getValues();
var offsetRow = range.getRowIndex(); // Added
for (var i = 0; i < values.length; i++) {
if (values[i][0]) { // Modified
ss.getRange("A" + (i + offsetRow) + ":P" + (i + offsetRow))
.setBorder(true, true, true, true, true, true, "black",
SpreadsheetApp.BorderStyle.SOLID_MEDIUM) // Modified
.setBackground('#FFFFFF');
}
}
}
Try this:
function onEdit(e) {
var sh=e.range.getSheet();
sh.getRange(1,1,sh.getMaxRows(),sh.getMaxColumns()).setBorder(false, false, false, false, false, false);
var range = sh.getRange(5,1,sh.getLastRow()-4,1); // Modified
range.setBorder(false, false, false, false, false, false);
var values = range.getValues();
for (var i=0;i<values.length;i++) {
if (values[i][0]) { // Modified
sh.getRange(i+5,1,1,16).setBorder(true, true, true, true, true, true, "black",SpreadsheetApp.BorderStyle.SOLID_MEDIUM).setBackground('#FFFFFF');
}
}
}
You cannot call this function from the script editor unless you supply the event object.

Borders to cells on the same line

I have a script to create cell borders when they are not empty:
function checkRange2(ss) {
var classeur = SpreadsheetApp.getActiveSpreadsheet();
var ss = classeur.getActiveSheet();
var range = ss.getRange("B5:B100");
range.setBorder(false, false, false, false, false, false);
var values = range.getValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] !== "") {
range.getCell(i + 1, j + 1).setBorder(true, true, true, true, true, true)
.setBackground('#FECB8D');
}
}
}
}
I wish that when the cell, B5 for example, contains a text, the range B5: E5 is with borders. And others lines too. Is it possible?
Cordially.
How about this modifications? I don't know whether I could understand what you want to do. If I misunderstand your question, please tell me. I would like to modify them.
In this modified script, at first, it retrieves the values of B5:E100. When strings are included in column B, the borders are given to cells from column B to column E for the row. For example, when a cell B5 has strings, the cells of "B5:E5" is surrounded by the borders.
Modification points :
When the column B is evaluated for the values retrieved from the range of B5:B100, it can be done as if (values[i][0]) { using one time of "for loop".
In this modified script, the range of B5:E100 is used. The values retrieved this range can be also evaluated by if (values[i][0]) {.
When you want to use the borders for the range of B5:E5, you can achieve this using range.setBorder().
At the script, the range is defined using the a1Notation obtained by adding offsetRow.
Modified sample :
function checkRange2(ss) {
var classeur = SpreadsheetApp.getActiveSpreadsheet();
var ss = classeur.getActiveSheet();
var range = ss.getRange("B5:E100"); // Modified
range.setBorder(false, false, false, false, false, false);
var values = range.getValues();
var offsetRow = range.getRowIndex(); // Added
for (var i = 0; i < values.length; i++) {
if (values[i][0]) { // Modified
ss.getRange("B" + (i + offsetRow) + ":E" + (i + offsetRow)).setBorder(true, true, true, true, null, null) // Modified
.setBackground('#FECB8D');
}
}
}
Note :
If you want to give the borders to all cells, please use .setBorder(true, true, true, true, true, null).
In your case, the borders are given to one row. So the last parameter of setBorder() can use null.
Reference :
setBorder()
If I misunderstand your question, I'm sorry.