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.
Related
I'm trying to use a macro to check the top row of my sheet and automatically center align all cells that have only a dash. Right now, it looks like my code is running, but it's not doing anything:
function CenterAlignAllDashes() {
var spreadsheet = SpreadsheetApp.getActive();
var topRow = spreadsheet.getRange('A1:1');
var topRowValues = topRow.getValues();
for (let i=0; i < topRowValues.length; i++) {
if (topRowValues[i] == '-') {
topRow[i].setHorizontalAlignment('center');
}
}
};
Modification points:
In your script, topRowValues is var topRowValues = topRow.getValues();. In this case, it is a 2-dimensional array. By this, topRowValues[i] == '-' is always false.
topRow is var topRow = spreadsheet.getRange('A1:1');. By this, topRow[i].setHorizontalAlignment('center') occurs an error because topRow[i] is undefined. But, by the above issue, the script in the if statement is not run.
About var topRowValues = topRow.getValues() and topRowValues.length, in this case, topRowValues.length is 1. By this, the for loop is looped only one time.
About spreadsheet.getRange('A1:1'), in this case, all cells of a row are retrieved.
When setHorizontalAlignment is used in a loop, the process cost will become high.
When these points are reflected in your script, it becomes as follows.
Modified script:
function CenterAlignAllDashes() {
// Ref: https://stackoverflow.com/a/53678158
const columnIndexToLetter_ = index => (a = Math.floor(index / 26)) >= 0 ? columnIndexToLetter_(a - 1) + String.fromCharCode(65 + (index % 26)) : "";
var sheet = SpreadsheetApp.getActiveSheet();
var topRow = sheet.getRange(1, 1, 1, sheet.getLastColumn());
var topRowValues = topRow.getValues()[0];
var ranges = [];
for (let i = 0; i < topRowValues.length; i++) {
if (topRowValues[i] == '-') {
ranges.push(`${columnIndexToLetter_(i)}1`);
}
}
sheet.getRangeList(ranges).setHorizontalAlignment("center");
}
When this script is run, first, the values are retrieved from the 1st row and create the range list. And, the alignment is changed using the range list.
References:
getLastColumn()
getValues()
getRangeList(a1Notations)
setHorizontalAlignment(alignment) of Class RangeList
Try this:
function CenterAlignAllDashes() {
const sh = SpreadsheetApp.getActiveSheet();
sh.getRange(1, 1, 1, sh.getLastColumn()).getValues().flat().forEach((v, i) => {
if (v == "-") {
sh.getRange(1, i + 1).setHorizontalAlignment("center");
}
});
}
I am trying to search column E for a cell starting with "X". I then want to move that entire row up to the top.
This is what I've created so far, using IndexOf:
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var Today = spreadsheet.getSheetByName("Today");
var TodayList = Today.getRange('E:E').getValues();
var i = TodayList.indexOf("X", 0);
Today.moveRows(Today.getRow(i), 1);
In your situation, how about the following modification?
In the case of Array.prototype.indexOf(), the values cannot be directly checked from the 2-dimensional array. But, in your situation, I thought that the 1st character can be directly checked using the index as follows.
Modified script:
function myFunction() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var Today = spreadsheet.getSheetByName("Today");
var TodayList = Today.getRange('E1:E' + Today.getLastRow()).getValues();
TodayList.forEach(([e], i) => {
if (e[0] == "X") {
var row = i + 1;
Today.moveRows(Today.getRange(`${row}:${row}`), 1);
}
});
}
When this script is run, the values are retrieved from column "E". And, each cell value is checked from the retrieved values. When the 1st character of the cell value is "X", the row is moved to the 1st row.
In this modification, the lower rows are moved to the upper row. If you want to do this with the reverse, please modify it as follows.
From
TodayList.forEach(([e], i) => {
if (e[0] == "X") {
var row = i + 1;
Today.moveRows(Today.getRange(`${row}:${row}`), 1);
}
});
To
var len = TodayList.length;
var offset = 0;
TodayList.reverse().forEach(([e], i) => {
if (e[0] == "X") {
var row = len - i + offset;
Today.moveRows(Today.getRange(`${row}:${row}`), 1);
offset++;
}
});
References:
forEach()
moveRows(rowSpec, destinationIndex)
Find X rows and move to top
function funko() {
const ss = SpreadsheetApp.getActive();
const tsh = ss.getSheetByName("Today");
const tvs = tsh.getRange(1, 1, tsh.getLastRow(), tsh.getLastColumn()).getValues();
let a = [];
let d = 0;
tvs.forEach((r, i) => {
if (r[4] == "X") {
a.push(r)
tsh.deleteRow(i + 1 - d++);
}
});
tsh.insertRowsBefore(1,a.length)
a.reverse();
tsh.getRange(1,1,a.length,a[0].length).setValues(a);
}
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 ...
The first part of my script is working fine as they are basic commands. The first clears the data present so it is ready to be re-written. The second sorts the data by region and start date. I am stuck on the 3rd part.
I have a set of dates in column C with a subject line in column E. I have corresponding dates in row 1 Columns L through NL. I am trying to write a script that will take the date of each row, compare it to the columns L:NL and if there is a match it will paste the subject in the corresponding cell.
I know there is something wrong with either my for statement or my if statement because right now it writes the subject of each row in column L only.
Here is an example spreadsheet.
function clear_Sort_Label() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
//Clear current data before resort and write
var rangesToClear = sheet.getRange("L3:NL");
rangesToClear.clearContent();
//Sort the data by region then start date
var range = sheet.getRange("A2:K");
range.sort([{column: 2, ascending: true}, {column: 3, ascending: true}]);
//Compare Start date (Column C) against dates in Row 1 Columns L:NL
//Each match paste value from subject (Column E) in coresponding cell
var lastRow = sheet.getMaxRows()
var lastCol = sheet.getMaxColumns()
for (var i=3; i<lastRow; i++) {
var meetingSubject = sheet.getRange(i,5).getValue();
var startDay = sheet.getRange(i,3).getValue();
Logger.log(startDay)
var day = sheet.getRange(1,12,1,lastCol).getValues()[0];
if(startDay = day){
sheet.getRange(i,12).setValue(meetingSubject);
}
}
}
You want to correspond the dates of column "C" and column "D" to the dates of "L1:NL1" as the range.
You want to put the value of column "E" to the first cell of the corresponded range.
You want to change the background color of the corresponded range to #b7e1cd.
If my understanding of your question is correct, how about this modification? From your question, I could understand that both the scripts of "Clear current data before resort and write" and "Sort the data by region then start date" work fine. So I modified about "the 3rd part". I think that there are several solutions for your situation. So please think of this as one of them.
Flow:
The flow of this modification is as follows.
Retrieve values of "C3:E".
Retrieve dates of "L1:NL1".
Compare each row of the values of "C3:E" to dates of "L1:NL1", and create an array for using at the output.
Create 2 new arrays for putting to the cells "L3:NL". Those are used for values and background colors.
Put values to 2 new arrays using the array created at section 3.
Put the values and set the background colors.
Modified script:
Please modify your script as follows.
From:
var lastRow = sheet.getMaxRows()
var lastCol = sheet.getMaxColumns()
for (var i=3; i<lastRow; i++) {
var meetingSubject = sheet.getRange(i,5).getValue();
var startDay = sheet.getRange(i,3).getValue();
Logger.log(startDay)
var day = sheet.getRange(1,12,1,lastCol).getValues()[0];
if(startDay = day){
sheet.getRange(i,12).setValue(meetingSubject);
}
To:
var values = sheet.getRange("C3:E").getValues().filter(function(e) {return e.some(function(f){return f})}).map(function(e) {return [e[0].getTime(), e[1].getTime(), e[2]]});
var dates = sheet.getRange("L1:NL1").getValues()[0].map(function(e) {return e.getTime()});
var res = values.reduce(function(ar1, e, i) {
var r = dates.reduce(function(ar2, f, j) {
if (f == e[0]) ar2.push([e[2], i, j]);
if (f == e[1]) ar2.push(j);
return ar2;
}, []);
if (r.length == 2) ar1.push(Array.prototype.concat.apply([], r));
return ar1;
}, []);
var row = values.length;
var col = dates.length;
var values = Array.apply(null, Array(row)).map(function() {return Array.apply(null, Array(col)).map(function() {return ""})});
var colors = Array.apply(null, Array(row)).map(function() {return Array.apply(null, Array(col)).map(function() {return ""})});
res.forEach(function(e) {
values[e[1]][e[2]] = e[0];
for (var i = e[2]; i <= e[3]; i++) {
colors[e[1]][i] = "#b7e1cd";
}
});
sheet.getRange("L3:NL" + (row + 2)).setValues(values).setBackgrounds(colors);
Note:
If you don't want to change the background colors, please remove .setBackgrounds(colors) from sheet.getRange("L3:NL" + (row + 2)).setValues(values).setBackgrounds(colors).
References:
Array.prototype.reduce()
Function.prototype.apply()
setBackgrounds(color)
If I misunderstand your question, please tell me. I would like to modify it.
Added:
If you want to use the sample spreadsheet including the internal headers, please modify as follows.
From:
var values = sheet.getRange("C3:E").getValues().filter(function(e) {return e.some(function(f){return f})}).map(function(e) {return [e[0].getTime(), e[1].getTime(), e[2]]});
var dates = sheet.getRange("L1:NL1").getValues()[0].map(function(e) {return e.getTime()});
var res = values.reduce(function(ar1, e, i) {
var r = dates.reduce(function(ar2, f, j) {
if (f == e[0]) ar2.push([e[2], i, j]);
if (f == e[1]) ar2.push(j);
To:
var values = sheet.getRange("A3:E").getValues().filter(function(e) {return e.some(function(f){return f})}).map(function(e) {return [e[0], e[2].getTime(), e[3].getTime(), e[4]]});
var dates = sheet.getRange("L1:NL1").getValues()[0].map(function(e) {return e.getTime()});
var res = values.reduce(function(ar1, e, i) {
var r = dates.reduce(function(ar2, f, j) {
if (e[0] && f == e[1]) ar2.push([e[3], i, j]);
if (e[0] && f == e[2]) ar2.push(j);
I have a Google Spreadsheet with about 20 sheet to create reports using some importrange functions. I am trying to get this script to work by looking through all the sheets on edit and putting a border around any cell that has contents but it does not seems to be working.
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var numSheets = ss.getNumSheets(); // count of sheets
// for every sheet
for( var s = 0; s < numSheets ; s++ ) {
var sheet = sheets[s]; // passes an individual sheet object to var sheet
//Add borders to non-empty
var range = SpreadsheetApp.getActiveSheet().getDataRange();
//set border
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);
}
}
}
}
}
I would suggest trying this function instead:
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var range = ss.getDataRange();
var maxCol = range.getLastColumn();
var maxRow = range.getLastRow();
for (var i=0; i<maxRow; i++)
{
for (var j=0; j<maxCol; j++)
{
if(!ss.getRange(i+1, j+1).isBlank()) //Check whether the cell is blank
{
ss.getRange(i+1, j+1).setBorder(true, true, true, true, true, true);
}
}
}
}
Reason being, the function you wrote runs everytime you make an edit to any of the sheets and iterates through all sheets. That is if you edit cell A14 on Sheet1, it will iterate through all 20 sheets checking the borders of the entire range for all sheets. Whereas, they might already have borders from it's previous run and nothing has changed in the other 19 sheets. This way, it iterates through the borders of the current sheet as soon as an edit is made on the sheet. However, I would like to bring 2 point for you to note with the current function:
onEdit only accepts user input as an "Edit".
Your sheet might end up looking something like this
Depending on what kind of data you're representing, this might or might not be what you desire. However, if I simplified the function a little more and used the following one:
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var range = ss.getDataRange();
range.setBorder(true, true, true, true, true, true);
}
I get the following sheet, with a much simpler and faster code:
The first scenario does exactly what your description desires, by adding a border to every cell that has data. However, the second one will only add borders to the extent of your data table in respect of maximum row and column. Hope this helps!