GAS : How to format all empty cells in a whole sheet - google-apps-script

I'd like to format all empty/blank cells in a whole sheet with a white background but I don't find the best method to retrieve a range that can handle all those empty/blank cells. I don't want to create a conditional rule, I'd like to handle it within a script.
What kind of method should I use ? Thanks a lot !

All empty/blank cells alas, as far as I know, there is no such range in SpreadsheetApp. You have to specify some max area. Say, "A1:ZZ". In this case the solution is quite trivial:
function set_all_backgrounds() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange('A1:ZZ');
var backgrounds = range.getValues().map(row =>
row.map(cell => { if (cell === '') return "white" } ));
range.setBackgrounds(backgrounds);
}
Of course if you will try to use column 'AAA' it could not have the white background.
If you mean all empty/blank cell inside data range, you can get the range this way:
var range = sheet.getDataRange();

Related

Is it possible to add formatting (shading) to rows being appended in Google Sheets (by Google Apps Script)

I've got a Google App Script which is copying rows from one sheet to another, performing various transformations. This logic ultimately gets rows onto the new sheet using sheet.appendRow(row detail). I would like these newly created rows to have a background colour (my intention is to hold a 'latestColour' so I can alternate the shading).
So, is there anyway to add shading within the appendRow method itself, or easily determine the range that the appendRow method processed, such that I can apply additional logic to add the shading.
You can use conditional formatting
=and(A1<>"",A2="")
Although I'm not sure whether I could correctly understand your situation, from your question, I thought that you might be using [Format] --> [Alternating colors] in Google Spreadsheet. And, when a new row is appended by putting the values, you might want to reflect "Alternating colors" in the appended row. If my guess is correct, how about the following sample script?
Sample script:
function myFunction() {
const addValues = ["sample1", "sample2", "sample3"]; // This is a sample appending value. Please replace this for your value.
const sheetName = "Sheet1"; // Please set the sheet name.
// Retrieve banding object from the data range.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const b = sheet.getDataRange().getBandings();
if (b.length == 0) {
console.log("Bandings are not used.");
return;
}
// Append the value.
sheet.appendRow(addValues);
// Expand the range of banding.
b[0].setRange(sheet.getDataRange());
}
When this script is run, the current banding is retrieved. And, after the value was appended, the banding is updated by including the appended row. In this sample, even when the multiple rows are appended, this script can be used.
Note:
From your question, I guessed that there is one banding in the data range in your sheet. Please be careful this.
References:
getBandings()
setRange(range)
Unfortunately the method appendRow() does not receive formatting settings as input, only an array of values.
However, here is a suggestion if you want to implement your own logic:
Sample code:
function applyColorLastRow() {
var ss = SpreadsheetApp.getActive(); //get active sheets file
var range = ss.getDataRange(); //get populated range, you may want to set a range manually if needed.
var lastRowNum = range.getLastRow(); //getting the last row index of the range.
var lastRowRange = ss.getRange(`${lastRowNum}:${lastRowNum}`); //narrowing the range (using A1 notation) to the last row only to apply color
var lastRowColor = lastRowRange.getCell(1,1).getBackgroundObject().asRgbColor().asHexString();
//Your row coloring logic here...
if (lastRowColor === '#ffffff'){ //toggling white/grey color as an example...
lastRowRange.setBackground('#cccccc'); //apply grey color to all cells in the last row range
} else {
lastRowRange.setBackground('#ffffff'); //apply white color to all cells in the last row range
};
}

Change cell value based on the cell's background color in Google Sheets

In Google Sheets, I would like to change the cell value (in this case to add text) based on the cell's background color. For example, if the cell's background color is green (#62a84f) as in the picture attached, I would like to automatically insert text "g" in the cell.
Please see the picture
I found the following script but cannot figure out how to get it working:
function myFunction() {
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Prosinec+Leden");
var data1 = sheet1.getDataRange().getValues(); //2D array of sheet1 cell values
var bg;
for(var i=1; i<data1.length; i++)
{
bg = sheet1.getRange(?).getBackground();
if(bg == "#62a84f")
{
sheet1.getRange(?).setValue("x"); //Set value in corresponding row of sheet1
}
}
}
*Little background info - I work in an elementary school where we evaluate the progress of our pupils every week and it would save us a lot of work to have this script work.
Explanation:
While that's might not be an issue, the color "#62a84f" you selected is not one of the default colors in the color picker menu. In order to test the following code, I used the default #34a853:
But if you are sure that your color is indeed "#62a84f" then go ahead and use that and change the relevant part of my code to your case.
I used double map to iterate over every cell in the rng. The bigger the range the slower the performance of the code. I would highly advice you to use some specific range and not the whole data range.
Namely, you might want to replace:
const rng = sheet1.getDataRange(); with const rng = sheet1.getRange('B2:D10');
assuming that you want to check for green cells in the range 'B2:D10'.
Another reason you would want to use this approach is that you may have some green cells with no content outside of the DataRange and therefore your range won't contain these cells. So think carefully what is your scenario and choose the method that suits you the best.
Solution:
function myFunction() {
const sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Prosinec+Leden");
const rng = sheet1.getDataRange();
const colors = rng.getBackgrounds();
const values = rng.getValues();
colors.map((r,i)=>r.map((c,j)=>{
if(c=='#34a853'){
values[i][j]="g";
}}));
rng.clearContent();
rng.setValues(values);
}

How to get background color of all cells in a range and set background to a different range of the same size using apps script?

I am trying to copy the background color from a range of cells and paste it to a different range. In my case, my desired color is the range between rows 3 to 53 and every other column from E to BC. In A1 format, it would be (['E3:E53','G3:G53','I3:I53', ... 'BA3:BA53','BC3:BC53']). I want to get the background color of this range and then paste it to my target range, being between rows 3 to 53 and every other column from D to BB. This range in A1 notation would be (['D3:D53','F3:F53','H3:H53' ... 'AZ3:AZ53','BB3:BB53']).
In other words, I want every cell in my target range to be the same color as the adjacent cell to its right.
This is what I currently have.
This is my desired outcome using apps script.
I know I can change color manually, but the values I have in my sheet change frequently and the color of the cells I want to copy is based on conditional formatting rules, meaning I would have to manually change all the cell colors on a regular basis. That is why I want to use apps script, so all I have to do is run a function and it will create my desired effect for me.
I am very new to the world of coding, but this is what I have tried.
setColor(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Coach");
var targetArea = ss.getRangeList(['D3:D53','F3:F53','H3:H53','J3:J53','L3:L53','N3:N53','P3:P53','R3:R53','T3:T53','V3:V53','X3:X53','Z3:Z53',
'AB3:AB53','AD3:AD53','AF3:AF53','AH3:AH53','AJ3:AJ53','AL3:AL53','AN3:AN53','AP3:AP53','AR3:AR53','AT3:AT53','AV3:AV53','AX3:AX53','AZ3:AZ53',
'BB3:BB53']);
var desiredColor = ss.getRangeList(['E3:E53','G3:G53','I3:I53','K3:K53','M3:M53','O3:O53','Q3:Q53','S3:S53','U3:U53','W3:W53','Y3:Y53',
'AA3:AA53','AC3:AC53','AE3:AE53','AG3:AG53','AI3:AI53','AK3:AK53','AM3:AM53','AO3:AO53','AQ3:AQ53','AS3:AS53','AU3:AU53','AW3:AW53','AY3:AY53',
'BA3:BA53','BC3:BC53']);
var background = desiredColor.getBackgrounds;
targetArea.setBackgrounds(background)
}
Iv'e ran the code, but it tells me that "targetArea.setBackgrounds is not a function". If I remove the "s" from .setBackground, it does not do anything at all. No error or anything.
Any help would be greatly appreciated! Thank you!
The setBackground() method on the RangeList object allows you to only provide one color. Instead, you want to be calling setBackgrounds() (or setBackgroundObjects()) on the individual ranges. You can get those by calling getRanges() and then iterating through them to apply whatever changes you want.
function setColor() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Coach");
var targetRangeList = ss.getRangeList(['D3:D53','F3:F53','H3:H53','J3:J53','L3:L53','N3:N53','P3:P53','R3:R53','T3:T53','V3:V53','X3:X53','Z3:Z53',
'AB3:AB53','AD3:AD53','AF3:AF53','AH3:AH53','AJ3:AJ53','AL3:AL53','AN3:AN53','AP3:AP53','AR3:AR53','AT3:AT53','AV3:AV53','AX3:AX53','AZ3:AZ53',
'BB3:BB53']);
var sourceRangeList = ss.getRangeList(['E3:E53','G3:G53','I3:I53','K3:K53','M3:M53','O3:O53','Q3:Q53','S3:S53','U3:U53','W3:W53','Y3:Y53',
'AA3:AA53','AC3:AC53','AE3:AE53','AG3:AG53','AI3:AI53','AK3:AK53','AM3:AM53','AO3:AO53','AQ3:AQ53','AS3:AS53','AU3:AU53','AW3:AW53','AY3:AY53',
'BA3:BA53','BC3:BC53']);
var sourceRanges = sourceRangeList.getRanges();
var targetRanges = targetRangeList.getRanges();
for (var i = 0; i < sourceRanges.length; i++) {
targetRanges[i].setBackgrounds(sourceRanges[i].getBackgrounds());
}
}
By the way, you can probably get those ranges in a more programmatic way since the target ranges seems to be odd-numbered columns while the source ranges are even-numbered and they're all of the same height.
Issues:
The line var background = desiredColor.getBackgrounds; does not
actually execute getBackgrounds() because you forgot the parenthesis.
However, even if you didn't forget it, both getBackgrounds() and
setBackgrounds() are methods of a range object, not a
rangeList object as you use in your approach.
Solution:
Since the ranges are non-Contiguous you can't just use getRange(). Instead you can create a list of ranges and iterate over them to get the desired result.
function setColor(){
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Coach");
const targetArea = ['D3:D53','F3:F53','H3:H53','J3:J53','L3:L53','N3:N53','P3:P53','R3:R53','T3:T53','V3:V53','X3:X53','Z3:Z53',
'AB3:AB53','AD3:AD53','AF3:AF53','AH3:AH53','AJ3:AJ53','AL3:AL53','AN3:AN53','AP3:AP53','AR3:AR53','AT3:AT53','AV3:AV53','AX3:AX53','AZ3:AZ53',
'BB3:BB53'];
const desiredColor = ['E3:E53','G3:G53','I3:I53','K3:K53','M3:M53','O3:O53','Q3:Q53','S3:S53','U3:U53','W3:W53','Y3:Y53',
'AA3:AA53','AC3:AC53','AE3:AE53','AG3:AG53','AI3:AI53','AK3:AK53','AM3:AM53','AO3:AO53','AQ3:AQ53','AS3:AS53','AU3:AU53','AW3:AW53','AY3:AY53',
'BA3:BA53','BC3:BC53'];
const targetArea_Ranges = targetArea.map(ta=>ss.getRange(ta))
const desiredColor_Ranges = desiredColor.map(dr=>ss.getRange(dr))
targetArea_Ranges.forEach((tr,index)=>{
tr.setBackground(desiredColor_Ranges[index].getBackground())
});
}
The previous answers are right. You're calling methods that are not available on class Rangelist. In this answer, I would like to add a alternative approach, based on the fact that all your ranges are contiguous and follow a 1 to 1 pattern:
get all of the ranges in 1 swing: D3:BC53
getBackgrounds and copy paste colors in memory in array
setBackgrounds the modified array.
function setColors() {
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Coach');
const allArea = sh.getRange('D3:BC53');
const backgrounds = allArea.getBackgrounds();
for (const row of backgrounds) {
for (let i = 0; i < row.length - 1; row[i++] = row[i++]);
}
allArea.setBackgrounds(backgrounds);
}

Count any colored cells in range

I am trying to create a Gantt Chart in Google Sheets, therefore I'll need to count all the colored cells, of any color, within a specific range, in order to get the duration of such task.
However, from what I've gathered, there's no built-in function.
https://www.youtube.com/watch?v=IuMSytD9t38 is the closest thing that I've found online, especially since it solves the "auto-refresh" problem. But it's not quite it.
Can anyone point me in the right direction?
If you want to use Google Scripts, you'll use the getBackgrounds() function for the cell range.
The following code added to a sheet via Google Scripts allows you to put =colors("a1:a5") and get all the non-white cells counted. You do have to put the cell array in quotes for it to work.
You can see it working on this sheet.
function COLORS(input) {
var ss = SpreadsheetApp.getActiveSpreadsheet();//get this doc
var sheet = ss.getActiveSheet();//get the active sheet
var counter = 0;//no colors yet
var range = sheet.getRange(input);//get range of cells from the function
var bgColors = range.getBackgrounds();//get the array of background colors
bgColors.forEach(function(element){
var cleanColors = arrayRemove(element,'#ffffff');//kick out the white backgrounds
counter = counter + cleanColors.length;//count them up
})
return counter;//return the total count
}
//kick things out of arrays from https://love2dev.com/blog/javascript-remove-from-array/#create-remove-method
function arrayRemove(arr, value) {
return arr.filter(function(ele){
return ele != value;
});
}
to count background color you can use this addon:
https://chrome.google.com/webstore/detail/custom-count-and-sum/njiklelndjpdbdngfkdgeijcpfabfgkb
and then you can simply use this formula:
=COUNTBACKGROUNDCOLOR("A2:A20", "A3")
A2:A20 being the range and A3 being the example of color to count

Google App script - setValues() doesn't work

So, I'm trying to write a script using the onEdit() event, which will basically remove links that are duplicates (technically, it removes everything, and only puts back things which aren't duplicates).
My code works fine all the way until it's time to write back non-duplicates. Namely, the line in which I use range.setValues(). I understand that it needs an array of arrays of cells which to edit, and that said array needs to fit in the range.
So far, I have :
if (unique)
{
newData.push(editedRow[0]);
Browser.msgBox(newData);
}
Unique is a variable I use that is false if an exact entry was found. With the msgBox command, I can verify that newData contains what it needs to contain. Further down, I have :
newDataFinal = [newData];
Browser.msgBox('Put values '+newDataFinal+' in range ' +range.getA1Notation());
range.setValues(newDataFinal);
To my knowledge, this should make NewDataFinal an array of arrays, which I can verify if I change setValues() to setValue(), which writes [[22.0, 13.0, 23.0]] (for my example) in the spreadsheet, which looks like an array of arrays to me.
The range should also match, since for this example, I get a prompt along the lines of "Put values 22,13,23 in range B2:B4" from the msgBox, which seems as a fitting range.
So, what am I doing wrong?
Here's the rest of the code (please excuse the abundancy of comments/msgboxes and lack of elegancy, the priority is to get it to work, I can probably optimize it and clean it up a bunch afterwards) :
function onEdit(e)
{
var range = e.range;
var values = range.getValues();
var sheet = SpreadsheetApp.getActiveSheet();
if (sheet.getName() != 'testiranje') return;
newData = new Array();
// Browser.msgBox(range.getA1Notation());
range.clear();
var data = sheet.getDataRange().getValues();
var counter = 0;
for (editedRowIndex in values)
{
unique = true;
editedRow = values[editedRowIndex];
// Browser.msgBox('Edited Row ' +editedRow);
for(i in data)
{
var row = data[i];
// Browser.msgBox('Old Row '+row);
for (j in row)
{
// Browser.msgBox(row[j] + ' vs ' + editedRow[0])
if (editedRow[0] == row[j])
{
Browser.msgBox('Hit! '+editedRow[0]);
unique = false;
}
}
}
if (unique)
{
// Browser.msgBox('Pushing '+editedRow[0]+' in newdata');
newData.push(editedRow[0]);
Browser.msgBox(newData);
}
}
newDataFinal = [newData];
Browser.msgBox('Put values '+newDataFinal+' in range ' +range.getA1Notation());
range.setValues(newDataFinal);
// range.setNote('SCIENCE');
}
I didn't test your code because I didn't feel like creating a sheet for it but what I can suggest (that should solve this issue in any case) is to replace your range.setValues(newDataFinal); with this :
sheet.getRange(range.getRowIndex(),range.getColumnIndex(),newDataFinal.length,newDataFinal[0].length).setValues(newDataFinal);
And if you want to know why the range and array didn't fit you can use this code :
(I used Browser because you seem to like it... I prefer Logger.log)
Browser.msgBox('data height = '+newDataFinal.length+', data width = '+newDataFinal[0].length+' and range height is '+range.getHeight()+', range width is '+range.getWidth()+' ... does it fit ?');
Note : I'm almost sure that your initial range is bigger than the newData array since you remove elements from the initial data... My best guess would be that heights don't fit. (but that's only a guess ;-) since you didn't mention the error message you get...)
the problem is that you cant change cells from an onEdit handler. see the docs. instead install your own onEditCustom handler.