Bug in getBackgrounds() using custom theme colors - google-apps-script

I've got a spreadsheet with colors from a custom theme.
When I read the colors using getBackgrounds() the colors returned are all #000000
If I read the colors cell-by-cell using getBackground() the colors are returned correctly.
If I use standard colors (ie not colors in my Theme) the colors are also returned correctly.
TEST SHEET
(available to view at https://docs.google.com/spreadsheets/d/1nCZeUbCjs_5p6_52v8ggqVgrgnJ-Pd6x-gzXUFfV8G0/edit?usp=sharing
Cells A1:D1 contain the names of the four Beatles, all with background color #b70906
TEST CODE
/** #OnlyCurrentDoc */
function getbackgroundstwoways(){
var fullrange= SpreadsheetApp.getActiveSpreadsheet().getRange("A1:D1");
// Read all cells using getBackgrounds
var arBack = fullrange.getBackgrounds();
var arValues=fullrange.getValues()
Logger.log("Full array " +arBack + arValues);
//Now do the cells individually with getBackground
for (var i=0; i<fullrange.getLastRow();i++){
for (var j=0; j<fullrange.getLastColumn();j++){
Logger.log("Single cell " + i + " " + j + " " + fullrange.offset(i,j).getBackground() + " " + fullrange.offset(i,j).getValue() ) ;
}}}
LOGGER OUTPUT
Full array #000000,#000000,#000000,#000000John,Paul,George,Ringo
Single cell 0 0 #b70906 John
Single cell 0 1 #b70906 Paul
Single cell 0 2 #b70906 George
Single cell 0 3 #b70906 Ringo

How about this answer?
Issue and solution:
When I saw your shared Spreadsheet, the background colors of cells "A1:D1" has the color type of "THEME". I think that this is the reason of your issue.
In the current stage, it seems that getBackground() can directly retrieve the background color from the color type of "THEME" as the hex string. But, it seems that getBackgrounds() cannot directly retrieve them. The retrieved values using it becomes #000000. I'm not sure whether this is the bug or the current specification. But in the current stage, the background colors of the color type of "THEME" can be retrieved by the methods in Spreadsheet service.
One of several solution is to use getBackground() as your script. This has already been achieved in your script.
In this answer, as another pattern, the colors are retrieved from the values retrieved by getThemeColors. The flow of this script is as follows.
Create an object for searching the colors from the theme color type.
Retrieve background objects.
Retrieve the background colors from backgroundObjects.
Sample script:
function getbackgroundstwoways() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const fullrange = ss.getActiveSheet().getRange("A1:D1"); // Range from your script.
// 1. Create an object for searching the colors from the theme color type.
const theme = ss.getSpreadsheetTheme();
const themeColorObj = theme.getThemeColors().reduce((o, e) => Object.assign(o, {[e]: theme.getConcreteColor(SpreadsheetApp.ThemeColorType[e]).asRgbColor().asHexString()}), {});
// const fullrange = SpreadsheetApp.getActiveSpreadsheet().getDataRange();
// 2. Retrieve background objects.
const backgroundObjects = fullrange.getBackgroundObjects();
// 3. Retrieve the background colors from backgroundObjects.
const backgroundColors = backgroundObjects.map(r => {
return r.map(c => {
if (c.getColorType() == SpreadsheetApp.ColorType.RGB) {
return c.asRgbColor().asHexString();
} else if (c.getColorType() == SpreadsheetApp.ColorType.THEME) {
return themeColorObj[c.asThemeColor().getThemeColorType()];
} else {
return null;
}
});
});
console.log(backgroundColors);
}
This script can be used for the color types of both "RGB" and "THEME". When this script is run at your shared Spreadsheet, [ [ '#b70906', '#b70906', '#b70906', '#b70906' ] ] can be seen at the log.
Even when the RGB types and the THEME types are mixed, this script can retrieve the background colors as the hex string.
Note:
In this case, the background colors are retrieved from the theme colors. But when you overcoat the background colors as the RGB type of #b70906, you can retrieve them using getBackgrounds().
When I searched about this that getBackgrounds() cannot be used for the THEME color type at the issue tracker, I couldn't find this. So how about reporting this? Ref
Please use this script with V8.
References:
getSpreadsheetTheme()
This method is added at December 18, 2019.
Class SpreadsheetTheme
getBackgroundObjects()

Related

Converting formatted Text from Google sheets to Google docs

I am trying to copy formatted texts from google sheets to google docs using google scripts. I have successfully converted text from sheets to docs however I am unable to carry over the relevant formatting/markdowns like bold, italics, colour, underlined & etc. Does anyone have any idea as to what I am doing wrong or what functions I can use in the google scripting library which allows me to copy over the formatting as well?
Currently, I have an existing google doc that acts as the template. All future google docs created will follow a similar template. I have created a sheet named 'doc Builder' and have used ,for loops and switch statements to choose which cell within the sheet to be copied over to the word doc.
function createDocument() {
var docbuilder = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('doc Builder');
//active data range
var range = docbuilder.getRange(4, 1, docbuilder.getLastRow() - 3, docbuilder.getLastColumn() - 1).getDisplayValues();
var templateId = 'myworddocIDwhichihaveremoved'; //the word doc
//Make a copy of the template file
var documentId = DriveApp.getFileById(templateId).makeCopy().getId();
//Rename the copied file
DriveApp.getFileById(documentId).setName('name of new doc');
//Get the document body as a variable
var body = DocumentApp.openById(documentId).getBody();
//copies texts from cell to word doc
//i = row, j = column
for(var i = 0; i < range.length; i++){
for(var j = 0; j < range[i].length; j++){
var cells = [];
switch(j) {
case 0:
body.appendParagraph(range[i][j]);
break;
case 1:
body.appendParagraph(range[i][j]);
break;
case 2:
if(range[i][j] != ""){
body.appendParagraph('PARAGRAPH 1:' + range[i][j]);
}
break;
case 3:
body.appendParagraph('PARAGRAPH 2:' + range[i][j]);
break;
}
}
}
}
I have tried copyTo() and it copies the formatting from sheet to sheet successfully however am unable to do the same for sheet to doc. I am also aware of the attributes which I can add to my word doc like BACKGROUND_COLOR, BOLD and etc from the documentation however the data I am handling often only has some parts of the cell formatted for example : sally is a girl instead of sally is a girl. Thus making it difficult to hard code when the number of cells increases.
Simply put I am trying to bring over the formatting from the sheet to the doc so I don't have to handle each cell individually.
I am working with more cases but I have removed them to simplify the code, also every cell within the active data range is formatted but when the new google doc is created the formatting disappears.
I hope someone has a solution to this haha :""D
Copying values from Sheets to Docs with formatting
There is no native method that you can use to copy formatted text from Sheets to Docs. They don't use the same classes to handle formatted text.
Sheets has RichTextValue that contains all the information for a cell. For example, when you call:
const range = sheet.getRange("A2")
const richTextValue = range.getRichTextValue()
You then can obtain all the information about the text formatting within the cell. For example, if you have a cell like this:
If you get the rich text value from this cell and then call the getRuns() method on this value you will get a series of new RichTextValue:
wherein each run is the longest possible substring having a consistent text style.
So for the example you will get a new object for:
"Hello"
"bold"
"italic"
... etc
You may also get individual object for the spaces between words.
For each of these objects, you can call a series of methods to get the individual components of its format:
getFontFamily()
getFontSize()
getForegroundColor()
isBold()
isItalic()
isStrikethrough()
isUnderline()
NOTE: getBackgroundColor() is not used in this example because background color in sheets cannot apply to single text runs, but the whole cell.
There is no equivalent class in DocumentApp. You can't append a RichTextValue to any element in a document. So this means that you need to match up the corresponding methods that you need. For example, you could use the Text class which has all the corresponding methods, you would just need a go-between to link up the methods and sequence them in the right way.
Example implementation
This would most likely need to be adapted to your exact needs, I don't know what the logic of the switch statements are and I don't have sample data to test it with, but this should give you a good idea of how it might work. You may also be able to use the custom class as-is in your script.
Ideally you would be able to call some simple methods from the main script, something like this:
function main() {
// Getting the rich text value
const sheet = SpreadsheetApp.getActive();.getSheetByName("Sheet1");
const range = sheet.getRange("A2");
const value = range.getRichTextValue();
// Creating an instance of a custom class that will be implemented
const textToExport = new SheetRichText(value)
// Setting the target document
const doc = DocumentApp.openById("[YOUR DOCUMENT ID]")
const body = doc.getBody()
// Calling a method of the custom class
textToExport.appendAsNewParagraph(body)
}
NOTE: Replace [YOUR DOCUMENT ID] with the correct document ID.
Remember that in my example my sheet has this:
The custom class I have implemented in my example is:
class SheetRichText{
// To initialize it you pass it a RichTextValue object
constructor(RichTextValue){
// It gets all the runs and creates an object that contains all the information
// needed to call the corresponding methods in the document Text class.
this.runs = RichTextValue.getRuns().map(run => {
const style = run.getTextStyle()
return {
"style" : {
"fontFamily" : style.getFontFamily(),
"fontSize" : style.getFontSize(),
"foregroundColor" : style.getForegroundColor(),
"bold" : style.isBold(),
"italic" : style.isItalic(),
"strikethrough" : style.isStrikethrough(),
"underline" : style.isUnderline()
},
"text" : run.getText(),
"start" : run.getStartIndex(),
"end" : run.getEndIndex()
}
})
}
// This takes as an argument the body of a document and adds the RichTextValue
// to the document as a new paragraph
appendAsNewParagraph(body){
// Initializing the new blank paragraph
const paragraph = body.appendParagraph("")
// For each run, copy the text and then set all the formatting
// making sure that the start and end indices are called.
this.runs.forEach(run => {
const textElement = paragraph.asText().appendText(run.text)
const [start, end] = [run.start, run.end -1]
textElement.setFontFamily(start, end, run.style.fontFamily)
textElement.setFontSize(start, end, run.style.fontSize)
textElement.setForegroundColor(start, end, run.style.foregroundColor)
textElement.setBold(start, end, run.style.bold)
textElement.setItalic(start, end, run.style.italic)
textElement.setStrikethrough(start, end, run.style.strikethrough)
textElement.setUnderline(start, end, run.style.underline)
})
}
}
Which results in:
References
Sheets RichTextValue
Docs Text

Google Sheets - Add to Certain Value Based on Background Color

I've tried making a Google Apps Script, but I was having trouble trying to understand how to set it up. From this it seems like I can create a function that I can call inside the spreadsheet itself like the SUM function provided by Google Sheets. I've taken a look at the getBackground() function, but it seems like it needs some global variables included instead of just functions.
Here's my current spreadsheet:
I want to input a function where it takes in the ranges A2:A1000 and based on the background color of the cell, determine whether it goes into "Work" or "Life" and then adds it onto the cells E4 (Total Work) or F4 (Total Life) accordingly. The cells in column A will always be numbers.
Here's what I've tried, I think I may be off the path completely based off of my single cell approach:
function workTime(input) {
if (input.getBackground() == "#d9ead3") {
input.setFontColor(4285f4)
} else {
input.setFontColor(null)
}
}
//I get errors on line 3 for some reason though...
TL;DR Based on the background colors of the cells, how do I create a function that calculates the sum of the numbers in those specific colors and displays them in different cells under the "Total Work Time" and "Total Life Time" accordingly?
The "custom formula" approach is very limited
The only input you'll get into the custom formulae are the values, not the cell object. The function that is running the formula will never know about its location or formatting. It receives a value or an array of values, and returns a value or am array of values.
Apps Script version
function workTime2() {
let file = SpreadsheetApp.getActive();
let sheet = file.getSheetByName("Sheet1");
let range = sheet.getRange('A1:A16');
let targetColor = "#00ffff"
let values = range.getValues(); // [[1],[2],[3]...]
let colors = range.getBackgrounds(); // [[#ffffff],[#00ffff],[#ffffff]...]
let sum = 0
for (let i = 1; i != values.length; i++){ // starting at 1 to skip first row
let value = values[i][0]
let color = colors[i][0]
if (color == targetColor) {
sum += value
}
}
let resultCell = sheet.getRange('B2');
resultCell.setValue(sum);
}
This script will sum the values in A1:A16 if they are turquoise. Putting the sum in B2.
This is a way to get a sum based of a cell value. This should give you a good starting point to customize to your liking.
Reference
getRange(a1Notation)
getValues()
getBackgrounds()
setValue(value)

Google Apps Script getBackgrounds for colour custom function

How do you write a custom function to get a cell's colour, or an array for a range's colour?
There's an example given by Google for how to optimise your functions when they use ranges, but how do you get data about the range rather than do maths on it?
Example given:
function DOUBLE(input) {
return Array.isArray(input) ?
input.map(row => row.map(cell => cell * 2)) :
input * 2;
}
Basic idea:
function COLOUR(input) {
return Array.isArray(input) ?
input.map(row => row.map(cell => cell.getBackground())) :
input.getBackground();
}
or maybe like this?
function COLOUR(input) {
return Array.isArray(input) ?
input.map(row => row.map(cell => getBackground(cell))) :
getBackground(input);
}
This code doesn't actually work, the first gives a type error and says you can't read getBackground of null, and the second one says getBackground is not defined, but hopefully you get what I'm trying to do. Help?
Issue:
getBackground() is a method applied to a range object. In your code, cell is a value.
Solution:
To get the background colors of a range, you need to get the range object of the particular cell references and then apply getBackgrounds to get the hex color codes
of your input.
You just need to pass the input as a string though e.g. =COLOUR("A1:B2").
function COLOUR(input) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const bcolors = sh.getRange(input).getBackgrounds();
return bcolors;
}
Example output:
If you want to get the color names, then you need to map the hex color codes (in bcolors) to actual color names and return the names instead.

Google Apps Script to set shape color

I was looking through the Google Apps Script reference here and noticed there is a method for setSolidFill(color).
I was wondering if it was possible to write a Google Apps Script to set shape colors based on values/ lookup reference in a Google Sheet? Essentially set the colour of Shape #001 in Google Slides to the HEX code in A2 of a Google Sheet?
I am wondering if it is possible to set shape colours based on colour codes found in a Google Sheet.
I think that your goal can be achieved. So in order to help to understand about the method for achieving your goal, I proposed the following 2 patterns.
Pattern 1:
In this pattern, the color of shape is changed using the object ID of the shape in 1st slide on Google Slides. In this sample, the hex color is retrieved from the cell "A1" of the Spreadsheet. Please set the Spreadsheet ID, sheet name and the presentation ID.
Sample script:
function myFunction() {
const objectId = "###"; // Please set the object ID.
const hexColor = SpreadsheetApp.openById("spreadsheetId").getSheetByName("sheetName").getRange("A1").getValue();
const slide = SlidesApp.openById("presentationId").getSlides()[0];
var obj = slide.getShapes().filter(s => s.getObjectId() == objectId);
if (obj.length > 0) obj[0].getFill().setSolidFill(hexColor);
}
Pattern 2:
In this pattern, the color of shape is changed using the shape type of the shape in 1st slide on Google Slides. In this sample, the hex color is retrieved from the cell "A1" of the Spreadsheet, and the color of "RECTANGLE" shapes are changed. Please set the Spreadsheet ID, sheet name and the presentation ID. Please select the shape type from Enum ShapeType.
Sample script:
function myFunction() {
const shapeType = "RECTANGLE"; // Please set the shape type.
const hexColor = SpreadsheetApp.openById("spreadsheetId").getSheetByName("sheetName").getRange("A1").getValue();
const slide = SlidesApp.openById("presentationId").getSlides()[0];
var objs = slide.getShapes().filter(s => s.getShapeType() == SlidesApp.ShapeType[shapeType]);
if (objs.length > 0) {
objs.forEach(obj => obj.getFill().setSolidFill(hexColor));
}
}
Note:
These are the simple sample scripts. So when you use the script, please modify it for your actual situation.
References:
setSolidFill(hexString)
getObjectId()
getShapeType()
Enum ShapeType
Added 1:
When you want to retrieve the object IDs of all shaped in a slide, you can use the following script.
Sample script:
const slide = SlidesApp.openById(presentationId).getSlides()[0];
const objectIds = slide.getShapes().map(s => s.getObjectId());
console.log(objectIds)
In this case, the object IDs of all shapes in 1st slide are put in an array.
Added 2:
For example, when the colors of all shapes in the 1st slide in Google Slides are changed to the red color, the following script can be used. When you want to select one of shapes using the object ID, at first, please retrieve the object IDs using the script of "Added 1", and use the script of "Pattern 1".
Sample script:
function myFunction() {
const hexColor = "#ff0000"; // This is a red color.
const slide = SlidesApp.openById(presentationId).getSlides()[0];
const shapes = slide.getShapes();
if (shapes.length > 0) {
shapes.forEach(obj => obj.getFill().setSolidFill(hexColor));
}
}
Added 3:
About can the pattern 1 script use an array (I need to change colours of several shapes, not just one), from your additional request of can you please show me how to adjust the pattern 1 script to work with an array?, I added one more sample script as follows.
In this sample, at first, please set the object IDs and hex colors in objectIds. By this, the colors of shapes of 1st slide can be changed.
Sample script:
function myFunction() {
const objectIds = [
{objectId: "###", hexColor: "###"},
{objectId: "###", hexColor: "###"},
,
,
,
];
const slide = SlidesApp.openById("presentationId").getSlides()[0];
const shapeObjects = slide.getShapes().reduce((o, s) => Object.assign(o, {[s.getObjectId()]: s}), {});
objectIds.forEach(({objectId, hexColor}) => {
if (shapeObjects[objectId]) shapeObjects[objectId].getFill().setSolidFill(hexColor);
});
}
Note:
This is a simple sample script. So please modify it for your actual situation.

Apply a "row banding" theme to a range

I am a beginner to Google Apps Script but use it to automate some simple repeating tasks. I have several spreadsheets I am copying content on a weekly basis and export them as an .xls file that I send to my client.
I am trying to apply alternating colors to a range I copy from another sheet but I completely got stuck. How to correctly set bandingTheme with the applyRowBanding method? What is the right syntax I should use in the last line of my code?
My code:
function copyRange (SourceSSID, SourceRange, TargetSheetName, bandingTheme) {
var sheetSource = SpreadsheetApp.openById(SourceSSID);
var sheetTarget = SpreadsheetApp.openById("bla-bla");
var source = sheetSource.getRange(SourceRange);
var target_ss = sheetTarget.getSheetByName(TargetSheetName);
var values = source.getValues();
var target = target_ss.getRange(1, 1, values.length, values[0].length);
target.clear();
target.setValues(values);
target.applyRowBanding ();
}
If your method argument bandingTheme is one of the enums listed here, you can simply apply it, using the apply___Banding(BandingTheme theme) method signature:
target.applyRowBanding(bandingTheme);
The above is equivalent to this line, per documentation:
target.applyRowBanding(bandingTheme, true, false);
(In other words, the default behavior is to color the header but not the footer, in addition to alternating row colors.)
You can ensure no existing themes were previously present (only a single kind of alternating colors - be it from columns OR rows - can be present at any given time, else an error is thrown).
target.getBandings().forEach(function (banding) {
banding.remove();
});
/**
* set the new banding theme
* ....
*/
If you wanted to set a custom banding theme, you can do so by starting from one of the theme designs. Note that the apply___Banding methods return the Banding object that they applied. If you bind this return value (or chain the methods), then you can modify it using its class methods.
const newBanding = target.applyRowBanding(SpreadsheetApp.BandingTheme.BLUE);
// newBanding is now a Banding that was instantiated with the "Blue" template.
// Color the header column:
newBanding.setHeaderColumnColor('teal');
// Equivalent:
target.applyRowBanding(SpreadsheetApp.BandingTheme.BLUE).setHeaderColumnColor('teal');
Note that setting colors for non-header columns in a row-banding theme doesn't work. Likewise for setting non-header row colors in a column-banding theme.
If your bandingTheme argument isn't one of the theme enums, then you will have to provide more details about what it is in order to get answers that help you convert it into the available Spreadsheet Service methods.
Here is a simple function that removes existing bandings and then applies alternating colors to an entire sheet. Please refer to Google Apps Script Range and Banding Class documentation for support.
function applyRowBanding() {
let sheet = SpreadsheetApp.getActiveSheet();
let range = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn());
range.getBandings().forEach(banding => banding.remove());
range.applyRowBanding(SpreadsheetApp.BandingTheme.LIGHT_GREY, true, false);
}