I have an issue that's likely due to my lacking understanding on how the getRange() function works.
I wrote two small functions to use in a Google Sheet, but I can't get cell references to work properly as input. Here is the code:
function getCellRGB(input, color) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange(input);
var hex = cell.getBackground();
hex = hex.replace('#','');
if (color == "r") return parseInt(hex.substring(0,2), 16);
else if (color == "g") return parseInt(hex.substring(2,4), 16);
else if (color == "b") return parseInt(hex.substring(4,6), 16);
else return null;
}
This only works if I input like this:
getCellRGB("A1", "r")
if I attempt to use a normal cell reference like they are used in other functions:
getCellRGB(A1, "r")
I get the error "Range not found (line 6)"
The second function colors a cell from a R, G and B value in one cell each:
function setCellColorFromRGB(red,green,blue) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var r = sheet.getRange(red);
var g = sheet.getRange(green);
var b = sheet.getRange(blue);
cell.setBackgroundRGB(r, g, b)
}
I get the same error for this one, also line 6.
Change this line
var cell = sheet.getRange(input);
to
var cell = sheet.getRange('"' + input + '"');
and you should be putting quotes around the passed value. You should be able to do the same for the other lines.
The getRange() method accepts several different types of arguments. For one cell you have two choices: a string in the a1 notation or two integers counting starting at 1.
So:
//these are valid and will get the top left most cell
var cell = sheet.getRange('A1');
var cell = sheet.getRange(1,1);
//this is not valid
var willNotWork = sheet.getRange(A1);
After you get the cell, you also need to call .getValue(); to get the contents of the cell.
Thanks everyone for your efforts, I've actually managed myself to find the solutions to them. There were quite a few more mistakes than I thought.
First of all, the code for my first function:
function getCellRGB(sheet, row, col, color) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheet);
var cell = sheet.getRange(row, col);
var hex = cell.getBackground();
hex = hex.replace('#','');
if (color == "r") return parseInt(hex.substring(0,2), 16);
else if (color == "g") return parseInt(hex.substring(2,4), 16);
else if (color == "b") return parseInt(hex.substring(4,6), 16);
else return null;
}
Issue, input.
It turns out when you call a function in sheets like so:
=getCellRGB(Sheet1, A1, A1, "r")
A1 and B1 actually return the values of those cells, not the reference to the cells themselves. In fact, it doesn't look like there is a way to easily input a cell reference into a function. I solved this by doing the input like so:
=getCellRGB(Sheet1 ROW(A1), COL(A1), "r")
Which is more complicated but works and even allows me to "formula paste" in Sheets.
Input the sheet
It's necessary to input the sheet where the cell to take the RGB from is located as I wanted it to be possible for it to be on a different sheet in the spreadsheet, not just the same sheet.
issue the getRange() function
This one took a while since I thought the only way to use it is with a cell reference as a String - getRange("A1"). While that is one way of doing it, it's also possible to input a row and column instead, to input which there are the defaul ROW() and COL() functions. That worked.
So this code works and I've used it to output the correct variables, but the issue is it turns out I was actually looking for a constantly running script since the RGB values were supposed to be read and output constantly instead of just once on entering the function. That made a different code entirely necessary which is really a different topic but since it may help people having the same realization, here it is:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var edit = ss.getSheetByName("Edit");
var calculation = ss.getSheetByName("Calculation");
var listenRange = edit.getRange("M2:M");
var applyRange = calculation.getRange("D2:F");
if (e.source.getSheetName() != "Edit") {
//ss.toast('nopeSheet' + ":" + e.source.getSheetName());
return;
}
var row = e.range.getRow();
if (row < 2) {
// ss.toast('nopeRow' + ":" + row);
return;
}
var col = e.range.getColumn();
if (col < 10 || col > 10) {
// ss.toast('nopeCol' + ":" + col);
return;
}
var row = e.range.getRow();
var col = e.range.getColumn();
var cell = edit.getRange(row,col);
var hex = cell.getBackground();
hex = hex.replace('#','');
var red = parseInt(hex.substring(0,2), 16);
var green = parseInt(hex.substring(2,4), 16);
var blue = parseInt(hex.substring(4,6), 16);
var redCell = calculation.getRange("D" + row);
redCell.setValue(red);
var greenCell = calculation.getRange("E" + row);
greenCell.setValue(green);
var blueCell = calculation.getRange("F" + row);
blueCell.setValue(blue);
}
There's some commented out debug code in there but otherwise this works with the exception of not triggering properly because the change of a background color apparently doesn't trigger onEdit. A fatal flaw but again, topic for another question and for everything but the color change this will work.
I've also hardcoded the sheet references and ranges but since I'm going to only use it for this sheet specifically that should be no issue.
Secondly regarding my other original piece of code - setting the background color of a cell based on RGB values in different cells. This turned out to be a major issue since changing a cells background color based on values in another cell is apparently not allowed for a function. I've managed to get around it with these two functions:
function rgbToHex(r,g,b) {
var r = parseInt(r);
var g = parseInt(g);
var b = parseInt(b);
if (g !== undefined)
return Number(0x1000000 + r*0x10000 + g*0x100 + b).toString(16).substring(1);
else
return Number(0x1000000 + r[0]*0x10000 + r[1]*0x100 + r[2]).toString(16).substring(1);
}
source: How to convert decimal to hex in JavaScript?
function setBgColor() {
var s = SpreadsheetApp.getActive().getSheetByName("Calculation");
var targetrange = s.getRange('L2:L').setBackgrounds(s.getRange('K2:K').getValues());
}
source: https://productforums.google.com/forum/#!topic/docs/88lWZY7WDZI
Since I only needed this as a one-time function, this worked well but it could possibly be converted to work differently as well.
Related
Completely new to using the AppsScript in Google but I came across a script that would allow GoogleSheets to count cells if coloured, and it automatically updates thanks to that last bit of code that sets a random value that essentially triggers a recalculation:
// Unsed third argument
function countColoredCells(countRange,colorRef,unUsed) {
var activeRg = SpreadsheetApp.getActiveRange();
var activeSht = SpreadsheetApp.getActiveSheet();
var activeformula = activeRg.getFormula();
// The regex matches the parentheses as an array, gets the arguments as a string,
// then splits the arguments on the comma into another array
var arrayOfArguments = activeformula.match(/\((.*)\)/).pop().trim().split(',');
// Get the first argument which is the range
var countRangeAddress = arrayOfArguments[0];
// Get the second argument, which is the reference color
var colorRefAddress = arrayOfArguments[1];
var backGrounds = activeSht.getRange(countRangeAddress).getBackgrounds();
var BackGround = activeSht.getRange(colorRefAddress).getBackground();
var countCells = 0;
for (var i = 0; i < backGrounds.length; i++)
for (var k = 0; k < backGrounds[i].length; k++)
if ( backGrounds[i][k] == BackGround )
countCells = countCells + 1;
return countCells;
};
// If Cell A1 has ColouredCells, Writes a random number to B1
function onEdit(e) {
if SpreadsheetApp.getActiveSheet().getRange('A1')="ColouredCells" {
SpreadsheetApp.getActiveSheet().getRange('B1').setValue(Math.random());}
}
Specifically I'm looking to edit the last part of the code so that this script only runs on sheets which I'd like it to (instead of randomly changing my B1 cell on all sheets I touch). As per the comments I'd like it to only run if cell A1 has the string "ColouredCells". Sorry newbie to all this so apologies if this is a really simple ask! Appreciate any help I can get on this one, I've tried googling quite a few different things but can't seem to to find the solution on this one!
This is the part I specifically need help with.
// If Cell A1 has ColouredCells, Writes a random number to B1
function onEdit(e) {
if SpreadsheetApp.getActiveSheet().getRange('A1')="ColouredCells" {
SpreadsheetApp.getActiveSheet().getRange('B1').setValue(Math.random());}
}
Thank you!
To run the code for specific sheets only you have to first get the name of the sheet and with an if-statement run the code as you wish
function onEdit(e){
// code
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
if ("Sheet 1" === sheet.getName() || "Sheet 2" === sheet.getName()) {
// Run your code
}
}
I have a button that I want to click, which will scroll me to a certain position. I've done this in order to get me to row 100:
function ScrollMe(){
var file = SpreadsheetApp.getActiveSpreadsheet();
var sheet = file.getActiveSheet();
var row = 100;
file.setActiveCell(sheet.getRange(row,1));
}
What I want to do if a find a list of all cells that are in column 'B' that contain (REGEX=>"Version: [0-9]?[0-9]?[0-9][.]?[0-9]?[0-9]? [a-zA-Z]+"), and then go to the last value that this is like. So basically, go to the last cell in column 'B' that starts with "Version: " and then has a single, double, or triple-digit number, a decimal point, and then two numbers after, and then any amounts of letter text after the fact. I want it to look like this:
function ScrollMe(){
var file = SpreadsheetApp.getActiveSpreadsheet();
var sheet = file.getActiveSheet();
//C# lambda
var row = FindAll(a=> a.COLUMN == 'B' && a.VALUE.RegexMatch("Version: [0-9]?[0-9]?[0-9][.]?[0-9]?[0-9]? [a-zA-Z]+"));
file.setActiveCell(sheet.getRange(row,1));
}
I assume that you expect the script to find the last cell in the column B that match your regex. If that is the case, you can use this code:
function ScrollMe() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getRange("B:B").getValues();
var regex = new RegExp(
'Version: [0-9]?[0-9]?[0-9][.]?[0-9]?[0-9]? [a-zA-Z]+');
for (var i = 0; i < data.length; i++) {
if (regex.test(data[i][0])) {
var lastMatch = i;
}
}
sheet.setActiveRange(sheet.getRange(lastMatch + 1, 2));
}
The previous code used your approach. It will first read the full column B, and after that will iterate to find the last cell that match the regex; and when it finds the cell, it will select it. Please, ask me if you have any doubts about the function.
In Google Sheets I'm trying to create a script that will take the value from the active cell and paste that value to any cell in Column B containing the string "HR". Any ideas?
This isn't too bad; you just have to wrap your head around a few concepts from Apps Script and Javascript to make it efficient. But first let's start with the naive approach!
function firstTry() {
var activeSheet = SpreadsheetApp.getActiveSheet(); // whatever is open
var activeCell = SpreadsheetApp.getCurrentCell(); // this is a single-cell range
var activeCellValue = activeCell.getValue(); // could be a string, number, etc
// Now let's look in column B for stuff to change
for (var i = 1; i <= activeSheet.getLastRow(); i++) {
var cell = activeSheet.getRange("B" + i);
var val = cell.getValue();
var valStr = String(val); // We could have gotten a number
if (valStr.indexOf("HR") != -1) {
cell.setValue(activeCellValue);
}
}
}
This will probably work, but isn't too efficient: each call to getValue() or setValue() takes some time. It'd be better to just get all the values at once, and then paste back a modified Column B when we're satisfied:
function improvement() {
var activeSheet = SpreadsheetApp.getActiveSheet(); // whatever is open
var activeCell = SpreadsheetApp.getCurrentCell(); // this is a single-cell range
var activeCellValue = activeCell.getValue(); // could be a string, number, etc
// Now let's look in column B for stuff to change
var rowsWithData = activeSheet.getLastRow() - 1;
var colBRange = activeSheet.getRange(1, // start on row 1
2, // start on column 2
rowsWithData, // this many rows
1); // just one column
// Let's get the data as an array of arrays. JS arrays are 0-based, btw
var colBData = colBRange.getValues();
for (var i = 0; i < colBData.length; i++) {
var val = colBData[i][0]; // row i, first column
var valStr = String(val); // We might have gotten a number
if (valStr.indexOf("HR") != -1) {
colBData[i][0] = activeCellValue; // modify copied data
}
}
// Lastly, write column B back out
colBRange.setValues(colBData);
}
You could go further with a fancy filter function instead of looping over the data explicitly, but that starts to get less clear.
Caveats as the OP points out in comments below, blindly calling setValues like this will pave over any formulas you have. This would have been no big deal, except that this includes hyperlinks. You could get really involved by calling getFormulas in parallel with getValues and then decide whether to call setValue or setFormula depending on the original contents of each cell.
I’m writing a custom function in Google Apps Script that, if a certain other cell contains a number, uses the value of that cell and several other cells to calculate a result. Otherwise, if that certain other cell does not contain a number, the function should just return an empty string so that the active cell appears blank.
I was able to come up with a working function to do this, but in order to protect sensitive information, I’m not going to copy it here. Instead, here’s an example function that accomplishes the same thing:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var rowNum = sheet.getActiveCell().getRow();
var rowVals = sheet.getRange(rowNum, 1, 1, 15).getValues();
var fVal = rowVals[0][5];
if (fVal == "" || isNaN(fVal)) {
return ""; //leave cell blank if column F doesn't contain a number
}
var aVal = rowVals[0][0];
var bVal = rowVals[0][1];
var cVal = rowVals[0][2];
var gVal = rowVals[0][6];
return ((gVal * fVal) + aVal + bVal + cVal);
}
However, in an effort to speed it up (and also some other reasons that would be complicated to try to explain here, so you'll have to just trust me), I want to have the custom function set the value of the cell to be a formula instead of doing the calculating itself. It doesn’t work to just put the formula in the cell in the first place because then it still calculates/shows a result even if column F doesn’t contain a number.
Here’s what I’ve tried so far:
function myFunction2() {
var sheet = SpreadsheetApp.getActiveSheet();
var rowNum = sheet.getActiveCell().getRow();
var fVal = sheet.getRange(rowNum, 6).getValue();
if (fVal == "" || isNaN(fVal)) {
return ""; //leave cell blank if column F doesn't contain a number
}
var formula = '=SUM((G2*F2)+A2+B2+C2)';
return formula;
}
^This just makes the cell display the string “=SUM((G2*F2)+A2+B2+C2)”.
So I then tried using setFormula on the active cell:
function myFunction3() {
var sheet = SpreadsheetApp.getActiveSheet();
var cell = sheet.getActiveCell();
var rowNum = cell.getRow();
var fVal = sheet.getRange(rowNum, 6).getValue();
if (fVal == "" || isNaN(fVal)) {
return ""; //leave cell blank if column F doesn't contain a number
}
cell.setFormula('=SUM((G2*F2)+A2+B2+C2)');
}
^which, when I called the function in a cell, returned an error saying “You do not have permission to call setFormula”. The same thing happened when I tried getting the a1 notation of the active cell and then using getRange(a1Notation).setFormula('=SUM((G2*F2)+A2+B2+C2)') instead of calling setFormula directly on the active cell.
Anybody know if there's a way around that permission error? or have any other ideas for how to accomplish this?
The permission error is because of restrictions on what user defined functions can do. You can, however, do this with onEdit like this:
function onEdit(e) {
var ss=SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getActiveSheet();
var col = e.range.getColumn();
var rowNum = e.range.getRow();
if(col==6){
var fVal = s.getRange(rowNum,col,1, 1).getValue()
}
if (fVal == "" || isNaN(fVal)) {
return
}
else{
s.getRange(rowNum,col,1, 1).setFormula('=SUM((G2*F2)+A2+B2+C2)');
}}
I actually ended up figuring out a way to accomplish what I wanted using the built-in IF function, so that's what I did.
I am trying to move the contents of column D to column A and keep them as formulas or values. The code below works but it takes FOREVER!!
I used this answer to put the values and formulas into an array:
How do I copy a row with both values and formulas to an array?
I used this suggestion to separate them out based on their type:
https://productforums.google.com/forum/#!topic/docs/JtcH-U3qC7s
var ss = SpreadsheetApp.getActiveSheet();
var formulas = ss.getRange("D2:D").getFormulas();
var values = ss.getRange("D2:D").getValues();
var merge = new Array(formulas.length);
for( var i in formulas ) {
merge[i] = new Array(formulas[i].length);
for( var j in formulas[i] )
merge[i][j] = formulas[i][j] !== '' ? formulas[i][j] : values[i][j];
}
for (k=0;k<merge.length;k++){
var rowRange = ss.getRange("A2");
var str = merge[k].toString();
var formulaChecker = str.substring(0,1);
if (formulaChecker == "="){
rowRange.offset(k, 0).setFormula(merge[k]);
}else{
rowRange.offset(k, 0).setValue(merge[k]);
}
}
Because it runs so slowly I feel like I missed something.
Is there a way to make it more efficient and run faster?
Try this:
function copyBoth() {
var sh = SpreadsheetApp.getActiveSheet();
var dataLngth = sh.getLastRow();
var rngCol_D = sh.getRange(2, 4, dataLngth, 1); //Set range for source - Example is column D
var formulas = rngCol_D.getFormulas(); //Get all the forumlas from the source range
var values = rngCol_D.getValues();
var rngCol_A = sh.getRange(2, 1, values.length, 1); //Set range for destination - Example is column A
rngCol_A.setFormulas(formulas);//Write all the formulas to the destination:
//Write all the values, individually to each cell.
var i = 0, thisFormula;
for (i=0;i<dataLngth;i+=1) {
thisFormula = formulas[i][0];
//Logger.log('thisFormula: ' + thisFormula);
//Logger.log('typeof thisFormula: ' + typeof thisFormula);
if (thisFormula === "") {
sh.getRange(i+2, 1).setValue(values[i][0]);//Write the individual value to the single cell
};
};
};
This code is very different from the code you are using. It sets all the formulas first. This eliminates the need to write every single cell. You still need to write values to individual cells that are in between the formulas, and that is done at the end. This example writes all the formulas at once, then the values one by one. But you could do it the opposite way. For example, if there are more values than formulas, it might save a couple of milliseconds.
This strategy also eliminates the need to merge the data, which is probably taking a lot of time.