I am trying to figure out how to use conditional formatting via script on a google spreadsheet similar to what you can do with the conditional formatting feature.
I have two columns labeled 'Country' and 'State.' If the value in one cell is 'United States,' I want the script to check the adjacent state column to make sure it's not blank. If it is, I want the cell background to change to red.
Is there any way to do this in a script? I don't want to use the built in feature as it doesn't copy over to newly create sheets within the spreadsheet as I'll be having other users creating new sheets.
I found some references, but I'm having trouble tailoring them to my needs.
Links: Google Spreadsheet conditional formatting script
https://webapps.stackexchange.com/questions/16745/google-spreadsheets-conditional-formatting-based-on-another-cells-content
Let's assume that Country is placed at (1,1) and State is placed at (1,2) where (i,j) indicates the ith row and jth column on the Spreadsheet. Google Spreadsheets is 1-indexed meaning indices start at 1.
var activeSheet = SpreadsheetApp.getActiveSheet();
for (var a = 2; a < activeSheet.getLastRow(); a++) {
if (String(activeSheet.getRange(a,1).getCell(1,1)) === "United States") {
if (String(activeSheet.getRange(a,2).getCell(1,1)) === null) {
activeSheet.getRange(a, 2, 1, 1).setBackgroundColor('red');
}
}
}
Try copy and pasting this into a blank script file. This depends on column A being Country, and column B being State, but you can make as many sheets as you want, and this will work automatically.
Let me know if you want an explanation of a specific part, as I'm not sure what your scripting background is. I've included some comments in the code.
function onEdit(e) {
var ss = e.source;
var sheet = ss.getActiveSheet();
var range = sheet.getRange("A:B");
var values = range.getValues();
//for each row that data is present
for(var i = 0; i < values.length; i++) {
var cell = sheet.getRange(i + 1, 2);
//check if the first value of that row is exactly "United States"
if(values[i][0] === "United States") {
//if it is, check for a blank cell. If so, make it red.
if(values[i][1] === "") {
cell.setBackground('red');
} else {
cell.setBackground('white');
}
} else {
//In any other case, make it white.
cell.setBackground('white');
}
}
}
Related
So I have multiple files that have a column where I would like to update in the formula. However, there might be a certain cell that already has a value in it, but I don't want to replace it with the formula (see screenshot for reference).
I read some references here, but haven't found a similar case like mine.
This is the attempt that I do, but it's not working:
function updateWithFormula(){
/*** Input Data From Multiple Sources ****/
var sourceWorkbook = SpreadsheetApp.openById('')//id of the workbook
//Open tab 'Sheet1' and pull the data inside the script
var sourceSheet = sourceWorkbook.getSheetByName('Sheet1')
var source = sourceSheet.getDataRange().getDisplayValues()
for(row in source){
if (source[row][3]=="Update Value") {
//open files through link
var files = SpreadsheetApp.openByUrl(source[row][2]) //there's a link inside this column that linked to the file that I want to update
/*******insert formula *******/
//get range that want to be inserted by the formula, which is column S
//if the column S already have value in it, I don't want to do anything in it, however if it doesn't have value, I would like to put a formula
var result = files.getSheetByName('Sheet1').getRange("S2:S") //this is the column that I want to update
//set formula
for(r in result)
{
if(result[r] == "")
result[r].setFormula("=R"+ r+1)
}
}
}
}
Do you guys have any idea why my code is not working? Any advice for this case?
Thank you!
Objective
If I understood correctly, your objectives are the following:
Retrieve data from a "master" spreadsheet with information on which spreadsheets to update.
Loop through said data and locate the spreadsheets (represented as rows) that require updating.
Open those spreadsheets individually.
Update those spreadsheets rows with a sheets formula if a certain condition is met (in this case, that the cell is blank).
Issues
The for(var a in b) syntax in javaScript is used to iterate through object, not arrays. You should change it to:
for (var i = 0; i<source.length; i++){
//YOUR CODE
}
where: source[i] lets you access that specific row.
When you try to get the individual sheets' values, you are actually only getting the range, not the values themselves. You should replace this:
var result = files.getSheetByName('Sheet1').getRange("S2:S")
with this:
var sheet = files.getSheetByName('Sheet1');
var range = sheet.getRange("S2:S");
var values = range.getValues();
(You can read more about ranges and how they work here).
To input values into a spreadsheet, you should do it by using the setValue() method in the range class. Again, go here for more info. So, instead of:
result[r].setFormula("=R"+ r+1)
use:
var rangeToModify = sheet.getRange(j, 19); //LETTER S IS THE 19TH
rangeToModify.setValue("=R"+ (j+1)); //SET THE FORMULA
Final Code
function updateWithFormula(){
var sourceWorkbook = SpreadsheetApp.openById('')//id of the workbook
//Open tab 'Sheet1' and pull the data inside the script
var sourceSheet = sourceWorkbook.getSheetByName('Sheet1')
var source = sourceSheet.getDataRange().getDisplayValues()
for(var i = 0; i<source.length; i++){
if (source[i][3]=="Update Value"){
var files = SpreadsheetApp.openByUrl(source[row][2]);
var sheet = files.getSheetByName('Sheet1');
var range = sheet.getRange("S2:S");
var values = range.getValues();
//set formula
for(var j = 0; j<values.length; j++){
if (values[j] == ""){
//GET THE RANGE THAT YOU WANT TO MODIFY
var rangeToModify = sheet.getRange(j, 19); //LETTER S IS THE 19TH
rangeToModify.setValue("=R"+ (j+1)); //SET THE FORMULA
}
}
}
}
}
I believe your current situation and your goal are as follows.
"Sheet1" of sourceWorkbook has the Spreadsheet URLs and the value of "Update Value" in the columns "C" and "D", respectively.
You want to retrieve the Spreadsheet from the URL, and want to check the column "S2:S" of of "Sheet1" in the retrieved Spreadsheet, and want to put a formula like "=R"+ r+1 to the non-empty cells of the column "S".
In this case, how about the following modification?
Modification points:
var result = files.getSheetByName('Sheet1').getRange("S2:S") returns Class Range object. This cannot be used with for(r in result). This is the reason of but it's not working. This has already been mentioned by the Oriol Castander's answer.
When setFormula is used in a loop, the process cost becomes high.
When these points are reflected in your script, it becomes as follows.
Modified script:
function updateWithFormula() {
var sourceWorkbook = SpreadsheetApp.openById(''); // Please set your Spreadsheet ID.
var sourceSheet = sourceWorkbook.getSheetByName('Sheet1');
var source = sourceSheet.getDataRange().getDisplayValues();
source.forEach(r => {
if (r[3] == "Update Value") {
var sheet = SpreadsheetApp.openByUrl(r[2]).getSheetByName("Sheet1");
var rangeList = sheet.getRange("S2:S" + sheet.getLastRow()).getDisplayValues().flatMap(([e], i) => e == "" ? [`S${i + 2}`] : []);
if (rangeList.length > 0) {
sheet.getRangeList(rangeList).setFormulaR1C1("=R[0]C[-1]");
}
}
});
}
In this modification, the formula is put as the R1C1 using the range list. By this, I thought that the process cost will be able to be reduced a little.
References:
getRangeList(a1Notations)
setFormulaR1C1(formula)
I have a Google Sheet file linked to a Google Form, that I use to record registrations for classes by different teachers. Inside of it I create extra columns that mark "X" in a row, when someone registers for that particular class, using an =IF function. I'd like those "X"'s to be checkboxes so that I can check attendees in the sheet itself as they arrive, but there is no method I can find for a function to output a tickbox into the cell and I have next to no knowledge of JavaScript.
Example Sheet: https://docs.google.com/spreadsheets/d/19BUkEfo8dWcAfPDhmhBTuhC1-V-QROlfF0pWH75c9VA/edit?usp=sharing
Ideally, I'd have a custom function that basically does the same as my =IF but inserts a checkbox instead of writing "X" (ie. if cell A contains text from cell B insert checkbox, else leave empty).
Alternatively I also found this solution to a similar problem, that I think would work, but I don't know enough to tweak it to my needs. This way I'd keep my sheet as is and the script would just replace the X's with checkboxes?
Probably it is not very efficient but it works:
// function creates menu 'SCRIPTS'
function onOpen() {
SpreadsheetApp.getUi().createMenu('SCRIPTS')
.addItem('Insert checkboxes', 'replace_x_to_checkboxes')
.addToUi();
}
// function replaces all 'X' on the sheet with checkboxes
function replace_x_to_checkboxes() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var data = range.getValues();
for (var row in data)
for (var col in data[row]) {
if (data[row][col] == 'X') sheet.getRange(+row+1,+col+1).insertCheckboxes()
}
}
To insert checkboxes without using formulas and 'X's you can run this function:
function insert_checkboxes() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var b1 = data[0][1]; // name from cell 'B1'
var c1 = data[0][2]; // name from cell 'C1'
for (var row=1; row<data.length; row++) {
if (data[row][3].indexOf(b1) > -1) sheet.getRange(row+1,2).insertCheckboxes();
if (data[row][3].indexOf(c1) > -1) sheet.getRange(row+1,3).insertCheckboxes();
}
}
Just in case. Instead of data[row][3].indexOf(b1) > -1 you can use a modern variant of the same condition data[row][3].includes(b1))
spreadsheet attached
The script below to clear fields works however, my formulas are also deleted.
I simply want to ensure the formulas are protected.
The formulas return information for the id entered into the cell (f16).
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Data Lookup');
var rangesToClear = ['F6', "F8", "F10", "F12", "F14", "F16"];
for (var i=0; i<rangesToClear.length; i++) {
sheet.getRange(rangesToClear[i]).clearContent();
var getCar = sheet.getRange("D6").getValue();
sheet.getRange("F6").setValue(getCar);
Logger.log(getCar)
}
}
I might be totally missing what you are trying to do, but I would leave the formulas and just remove the value in F16. The formulas would still look for a blank cell and show the results of the blank ID - also blank.
Or change the formulas to IF(F16="","",yourformula)
The filter() formulas will error out when the search key is blank, because it will match all blank rows in Data. Replace them with vlookup() formulas like this:
=iferror(vlookup(F16, { Data!F2:F, Data!A2:A }, 2, false))
Use Data > Protected sheets and ranges to protect the range F6:G14.
In your script, only clear cell F16, and leave other cells untouched.
Modified script
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Data Lookup');
var rangesToClear = ["F14", "F16"];
for (var i=0; i<rangesToClear.length; i++) {
sheet.getRange(rangesToClear[i]).clearContent();
}
}
One change made here was that values were removed rangesToClear array. Previously it was:
var rangesToClear = ['F6', "F8", "F10", "F12", "F14", "F16"];
And now its:
var rangesToClear = ["F14", "F16"];
Which I believe corresponds to the "Phone" and "ID number" fields in your screen shot.
So now the script will only clear the ranges that don't have formulae.
The other change I made was to delete this:
var getCar = sheet.getRange("D6").getValue();
sheet.getRange("F6").setValue(getCar);
Logger.log(getCar)
As this seems to fetch a value from D6 which in you example is just the lable "Date:" (I don't know why the variable is called getCar), and then puts that in F6, which you seem to want to keep as a formula.
Reference
Range Methods
I'll start this off by saying I have no clue what I'm doing. I'm surviving off copying and pasting code off the internet for a spreadsheet me and my friends use for watching films together.
I've run into an issue where I'm updating a cell with the current date when another cell in that row is updated if its blank with a script.
This issue is I then use a function in the cell next to it to give the difference in days for another date marked down in a cell (like a normal spreadsheet as that easier for me to do). But every time the script runs the function breaks and is replaced with the text "#NUM!" (Actually has that text as the function disappears from inside it).
I tried changing it to =U2 and that breaks also. Is this something that can't be done? The great almighty google god has not provided me with an answer so I've made an account here in hope of salvation.
tl;dr Scrips look like they are breaking my cell references for any sheet function that looks at cells they edit. How stop?
In cell V2 I have the function =DATEDIF(S2,U2,"D")
Script bellow (I know not how to format)
function onEdit(event) {
var eventRange = event.range;
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();
if (sheetName == "Scores") {
if (eventRange.getColumn() == 10) { //Check which is updated
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);//where to write
var values = columnXRange.getValues();
for (var i = 0; i < values.length; i++) {
if (!values[i][0]) { // If cell isn't empty
values[i][0] = new Date();
}
}
columnXRange.setValues(values);
}
}
}
Ok, I see the problem. You are looking at a way bigger range than you want with
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);
You only really need the value of one cell to check if it is empty. Try replacing your function with :
function onEdit(event) {
var eventRange = event.range;//makes shit happen?
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();//checks current shit
if (sheetName == "Scores") {//name of sheet want shit to happen
if (eventRange.getColumn() == 10) { // 1 is column A, 2 is B ect
// getRange(row, column, numRows, numColumns) sheet name to make not everywhere
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, 1, 1);//num is where will write 1 is a ect
var values = columnXRange.getValues();//takes all shit from above to use as range
if (!values[0][0]) { // If cell isn't empty
values[0][0] = new Date();//set date to the vaules in the range
}
columnXRange.setValues(values); //use the values set above and write them in
}
}
}
..and that should fix your problem. The problem with your current script is that the script is copying the "value" of your column v cells and replacing it with just a text value. This limits the range you are grabbing to just the cell you need, eliminates the for() loop, and steps over the problem entirely.
I have a Spreadsheet whose first four columns (A, B, C, D) are dynamically imported from a separate master Spreadsheet.
Columns E, F are designed to take static inputs based on the dynamic data in the columns to their left.
Column A contains an individual's name.
I want to use column G as a static reference to column A to ensure the inputs in columns E & F can be easily maintained in the correct rows via manual cut and paste when the dynamic data in columns A through D move as new inputs arrive from or are removed from the master sheet.
To do this, I want a script that 'conditionally formats' the entries in column G when they match those in column A (bold, italicize, color the text), AND does not fall afoul of the issue associated with cutting and pasting using the standard UI conditional formatting in google sheets, where a cut and paste will 'break' the conditional formatting range.
We'll copy the values in dynamic column A over to static column G and then reference back to A using conditional formatting.
I have a basic script here (gleaned from another stackoverflow poster), but it is going to need some work to do what I need it to do.
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("A:G");
var values = range.getValues();
//for each row that data is present
for(var i = 0; i < values.length; i++) {
var cell = sheet.getRange(i + 1, 7);
//check if the first value of that row is exactly "Client Name"
if(values[i][0] === "Client Name") {
//if it is, check for "Client Name" in the same row in G [6]. If so, make
it green.
if(values[i][6] === "Client Name") {
cell.setBackground('green');
} else {
cell.setBackground('white');
}
} else {
//In any other case, make it white.
cell.setBackground('white');
}
}
}
I want the script to check if the value in cells A3:A are equal to those in G3:G and if they are to format the text in G3:G as indicated.
A "mock up" of sheet with existing script is here:
https://docs.google.com/spreadsheets/d/13iPM83I5ecskuBaBin8hepTyBqD29ng0Zp3e6DBcuEk/edit?usp=sharing
Thanks for all help!
If you set your conditional formatting to use the custom formula
=AND($G1=$A1, NOT(ISBLANK($A1)))
and then you use "Paste values only" (Edit > Paste special or cmd + shift + v, ctrl if on Windows), then you will not need a script.
Using conditional formatting and pasting values will be much faster.
Otherwise, you can use this script for changing the formatting in column G when it doesn't match the respective value in A.
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1"); // getActiveSheet() can yield unexpected results
var rangeA = sheet.getRange("A:A");
var rangeG = sheet.getRange("G:G");
var valuesA = rangeA.getValues();
var valuesG = rangeG.getValues();
var backgrounds = []; // Create an array for the background colors
var fontColors = []; // Create an array for the font colors
//for each row that data is present
for(var i = 0; i < valuesA.length; i++) {
var aValue = valuesA[i][0];
var gValue = valuesG[i][0];
if((aValue != "") && (gValue === aValue)) { // If value in A is NOT blank AND A == G
backgrounds.push(["green"]);
fontColors.push(["white"]);
} else {
backgrounds.push([null]); // Using null will reset the background color formatting
fontColors.push([null]); // Using null will reset the font color formatting
}
}
rangeG.setBackgrounds(backgrounds); // Set the background colors all at once for speed.
rangeG.setFontColors(fontColors); // Set the font colors all at once for speed.
}