Google Sheets converting Conditional Formatting to Apps Scripts - javascript

I'm sure that there's not an easy way to do this, but I figured I would ask. I have a sheet that has a decent amount of conditional formatting on it, however, people that I share the sheet with have been copy/pasting and moving rows which aggravatingly disrupts the formatting. I am trying to convert to Scripting for them. The main thing is background colors based on other cell information.
ie: if Any cell from row 4 from column A-D has information in it then the K4 will be red. Once K4 has information inside it then the background will be white.
The conditional formatting I have is: =AND(OR(ISBLANK(A4)=FALSE,ISBLANK(B4)=FALSE,ISBLANK(C4)=FALSE,ISBLANK(D4)=FALSE),ISBLANK(K4)=TRUE).
The other is that when any cell has a certain term "LWOS" then the whole row has a purple background and strikethrough.
Thanks in advance.

I know scripting is hard when you are new to it, so I understand you and decided I'm going to give you a code that should work with yours. And please do follow Endothermic_Dragon's recommendation into studying Apps Script if you want to learn more.
Code:
function onEdit(e) {
// Get active spreadsheet and sheet
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
// Get row and column of the current cell edited
var editRow = e.range.getRow();
var editColumn = e.range.getColumn();
// If edited cell is below row 4:
if(editRow > 3) {
// Get row and K cell for checking values later
var rowRange = sheet.getRange("A" + editRow + ":D" + editRow)
var rowValues = rowRange.getValues();
var kCell = sheet.getRange("K" + editRow);
var kValue = kCell.getValue();
var rowHasValue = rowValues[0].some(el => !!el);
var rowHasLWOS = rowValues[0].includes("LWOS");
var kHasValue = kValue != "";
// If edited column is in A-D
if(editColumn < 5) {
// If we see a value on that row and K row doesnt have value
if(rowHasValue && !kHasValue){
// Set K cell to red
kCell.setBackground("red");
}
// If columns A-D of the edited row has LWOS
// (cell value is "LWOS", not cell containing LWOS)
if (rowHasLWOS){
// Set row to purple and strikethrough
sheet.getRange(editRow + ":" + editRow).setBackground("purple").setFontLine("line-through");
}
}
// If edited column is K
if(editColumn == 11) {
// If row doesnt have LWOS, proceed with checking the K value
if(!rowHasLWOS) {
// If edited value of K has value
if(kHasValue) {
// Set K to white
sheet.getRange("K" + editRow).setBackground("white");
}
// If K value was deleted and A-D has values
if(rowHasValue && !kHasValue) {
// Set K back to red
sheet.getRange("K" + editRow).setBackground("red");
}
}
}
}
}
Output:
This has comments on it so hopefully you can at least understand what the code does and how it works.
For more details, check the references you can study below.
References:
Spreadsheet
Spreadsheet tutorials
Beginners guide by Ben Collins

Related

Using Apps Script for Google Sheets, Insert a Header Row after change in cell value

I have not done any scripting for about 7 years. I used to write a lot of VBA scripts with SQL for Access and Excel. I am now trying to automate a Google sheet row insertion with formatting and a cell value edit in the new row.
I am hoping someone who can script with their eyes closed will help this old lady with a script to automate a volunteer task for an all volunteer food coop so that I can pass this task on to someone with less spreadsheet skill.
I have written the task in my own fudgy language and am hoping someone can translate it into the proper language and syntax. Here it is:
function (createReceivingSheet)
for each cell in range A2: A500
if right(this.cell, 6) != right(this.cell.-1, 6)
insert.row.above(this.cell)
format(new row) bold, underline, font:arial, 12pt
merge (newrow.column1:column5)
format (newrow.cell.column1) border:bottom
case edit(newrow,cell.column1)
when original.cell = "02 GM *" then "GO MACRO",
when original.cell = "000 *" then "PRODUCE"
end
End function
In other words I want to insert a formatted title row above each change in vendor where the vendor code is the first 6 characters of the cells in column A.
I need the script to iterate through Col A:
compare each cell with the cell above
if the first 6 characters of the current cell are not equal to the first 6 characters of the cell above, then insert a row above
format the newly inserted row in bold, underline, 12 pt
merge the first 5 columns in the newly inserted row
format the merged cells in the newly inserted row with a bottom border
populate the first cell (column A) of the newly inserted row with a value based on a set of case statements when the original cell = "X" or "Y" or "Z" etc.
I do not know if this is an appropriate question to ask on this forum. Please let me know if you can help or if this is too much to ask on this forum.
I think this covers most of your pseudocode. At the bottom, you'll have to add the values that you want to put into the new cells.
This onOpen function adds a new menu after Help. Select a range then use the menu item to run partitionVendors.
There's good documentation here for when you need to add more features: https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app?hl=en
const VENDOR_PREFIX_LENGTH = 6;
const MENU_TITLE = "Stack Overflow";
/**
* Adds a menu to the spreadsheet when the file is opened
*/
function onOpen(e) {
var ui = SpreadsheetApp.getUi();
const menu = ui.createMenu(MENU_TITLE)
menu.addItem("partition vendors", "partitionVendors")
.addToUi();
}
function partitionVendors() {
const sheet = SpreadsheetApp.getActiveSpreadsheet();
const range = SpreadsheetApp.getActiveRange();
for (let r = range.getNumRows(); r > 1; r--) {
const currentCellValue = range.getCell(r, 1).getValues()[0][0];
const previousCellValue = range.getCell(r - 1, 1).getValues()[0][0];
if (currentCellValue.slice(0, VENDOR_PREFIX_LENGTH) !== previousCellValue.slice(0, VENDOR_PREFIX_LENGTH)) {
const newRowNum = range.getLastRow() - range.getNumRows() + r;
sheet.insertRowBefore(newRowNum);
// assuming the selection is in column A, merge columns A to E
const newCellRange = sheet.getRange(`A${newRowNum}:E${newRowNum}`);
newCellRange.merge()
.setFontWeight("bold")
.setFontLine("underline")
.setFontFamily("Arial")
.setFontSize(12)
.setBorder(null, null, true, null, null, null);
// populate values here
if (currentCellValue === "value1") {
newCellRange.setValue("header for value1");
} // else if ...
}
}
}

Paste data starting in column P of google sheets (via script)?

I'm trying to post an array into column P daily. Because it's posting daily, I'd like it to also grab the last row.
I'm pulling from another workbook but don't know how to get the paste to work.
Function () {
var toCopy = [copies data from spreadsheet 1]
/* trying to paste into Aggregate tab in spreadsheet 2*/
var ss2=SpreadsheetApp.openById(‘spreadsheet 2’);
var tsh2=ss2.getSheetByName('Aggregate');
var tsh2.getLastRow(???
}
How do I paste var toCopy into column P? The array is 15 columns wide.
You want to append the values to the next row of the last row of column "P" in the sheet of "Aggregate".
The values are 2 dimensional array which has 600 rows and 15 columns.
The number of rows is changed every run.
The number of columns is not changed.
After the values are put, you want to retrieve the last row number.
I could understand like above. If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Flow:
Retrieve the last row number of the column "P".
From your question, I'm not sure whether the last row of the column "P" is the last row of the sheet. So the last row is checked for only column "P".
Append "toCopy" to the next row of the last row of column "P".
Retrieve the current last row number.
Modified script:
Please set the values of toCopy and the Spreadsheet ID.
function myFunction() {
var toCopy = [copies data from spreadsheet 1]
var ss = SpreadsheetApp.openById('###');
var sheet = ss.getSheetByName('Aggregate');
// Retrieve the last row of the column "P".
var lastRowOfColP = 0;
var valuesOfColP = sheet.getRange("P1:P").getValues();
for (var i = valuesOfColP.length - 1; i >= 0; i--) {
if (valuesOfColP[i][0]) {
lastRowOfColP = i + 1;
break;
}
}
// Put "toCopy" to the next row of the last row of column "P".
sheet.getRange(lastRowOfColP + 1, 16, toCopy.length, toCopy[0].length).setValues(toCopy);
// Retrieve the current last row number.
var currentLastRow = lastRowOfColP + toCopy.length;
Logger.log(currentLastRow)
}
This modified script supposes that toCopy is 2 dimensional array.
References:
setValues(values)
getRange(a1Notation)
getRange(row, column, numRows, numColumns)
Looks like Set a range's values is what you're after.
function () {
var values = [
// Copied data from spreadsheet 1
]
// Paste into Aggregate tab in spreadsheet 2
var ss2 = SpreadsheetApp.openById(‘spreadsheet 2’);
var sheet2 = ss2.getSheetByName('Aggregate');
var range = sheet2.getRange('P1:P15');
range.setValues(values);
// Grab last row
var lastRow = sheet2.getLastRow();
}
PS: Watch out for the formatting in your code. It looks like you might not be using a proper text editor. Your formatting made function be Function and the quotations 'spreadsheet 2' become ‘spreadsheet 2’. I use VS Code and Vim, for example.

Check if a column header title matches a string and if so then return the column index

I am trying to write a script for google sheets which returns the date in the next cell when the user enters 'y' in the current cell. I have a script which does this already, but the problem with my script is that the columns which it is evaluating is based on the column index, which means if our data set ever grows then these columns always have to stay in the same index which is creating a lot of organizational issues.
My question is..
Is it possible to look for the column header title rather than the column index in my code, and if so, what changes would I need to make?
function onEdit(e) {
if ([19].indexOf(e.range.columnStart) == -1 || ['y', 'Y'].indexOf(e.value) == -1) return;
e.range.offset(0, 1)
.setValue(Utilities.formatDate(new Date(), "GMT-5", "MM-dd-yyyy"))
}
This code currently looks at column index 19 and when either 'y' or 'Y' is entered into a cell in column index 19 it then outputs the date in the next cell in column 20.
How can I change the code to look for where the column header = 'Replied?' rather than index?
Goal:
If the following criteria is met:
Value is written into column 19 (S).
Header of column 19 (S) is 'Replied?'.
Value written is either 'Y' or 'y'.
Then write a date into the adjacent cell.
Code:
function onEdit(e) {
var sh = e.source.getActiveSheet();
var row = e.range.getRow();
var col = e.range.getColumn();
var value = e.value.toUpperCase();
var header = sh.getRange(1, col).getValue();
if (col === 19 && value === 'Y' && header === 'Replied?') {
sh.getRange(row, 20).setValue(Utilities.formatDate(new Date(), "GMT-5", "MM-dd-yyyy"))
}
}
Explanation:
I've based everything on the event objects passed to your onEdit trigger. For var value I have used toUpperCase() so that we don't have to check for either 'Y' OR 'y', only 'Y' alone. Also, instead of using range.offset I have just specified column 20 specifically in the getRange().setValue().
References:
Event Objects
String.toUpperCase()
One possible way to do this is to name the column/ cell in google sheets. See this website on how to.
Basically:
Open a spreadsheet in Google Sheets.
Select the cells you want to name.
Click Data and then Named ranges. A menu will open on the right.
Type the range name you want.
To change the range, click Spreadsheet Grid.
Select a range in the spreadsheet or type the new range into the text box, then - click Ok.
Click Done.
You can then refer to that named cell in google scripts by creating a custom function
function myGetRangeByName(n) { // just a wrapper
return SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n).getA1Notation();
}
Then, in a cell on the spreadsheet:
myGetRangeByName("Names")
I'd do this.
function onEdit(e) {
var editedColumn = e.range.columnStart;
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getSheetByName("This");//you only want onedits to the specific page
var data = ss.getDataRange().getValues();
var header = data[0][editedColumn];
if (header != "Replied") return;
if(e.value.toLowerCase() == "y"){
e.range.offset(0, 1)
.setValue(Utilities.formatDate(new Date(), "GMT-5", "MM-dd-yyyy"));}
}
You could also consider using a checkbox, that might be faster for your users.

Delete rows script on multiple sheets even when some don't have empty rows

I have a script that deletes empty rows at the bottom of about 15 sheets every night after work is done on the spreadsheet during the day. I run this because empty rows are left at the bottom very often (but not always) and this helps to clean it up automatically without me having to worry about it. It works perfectly fine when empty rows are left at the bottom, but when there aren't and the sheet is full of data all the way down, it gives me an error and won't continue to the next sheet. I assume it has to do with the
lastRow + 1
but I don't know how to change it to still run properly. My main question is, how can I still run this code as normal (deleting empty rows on listed sheets), but if there aren't any empty rows, it just goes to the next sheet instead of throwing up an error?
// DELETE EMPTY ROWS AT BOTTOM
function RemoveEmptyRows() {
var ss = SpreadsheetApp.openById('spreadsheetId');
var arrayOfSheetTabNames, i, L, sheet;
arrayOfSheetTabNames = [
"Sheet1",
"Sheet2",
"Sheet3",
"Sheet4",
"Sheet5",
"Sheet6",
"Sheet7",
"Sheet8",
"Sheet9",
"Sheet10",
"Sheet11",
"Sheet12",
"Sheet13",
"Sheet14",
"Sheet15"
]
L = arrayOfSheetTabNames.length;
for (i = 0; i < L; i++) {
sheet = ss.getSheetByName(arrayOfSheetTabNames[i]);
var maxRows = sheet.getMaxRows();
var lastRow = sheet.getLastRow();
sheet.deleteRows(lastRow + 1, maxRows - lastRow);
}
};
Thank you for any help!
You want to delete all empty rows below the data range for 15 sheets.
There are several sheets which has no empty rows.
If my understanding is correct, how about this modification? In your situation, for example, how about comparing lastRow with maxRows?
Modified script:
Please modify as follows and try again.
From:
sheet.deleteRows(lastRow + 1, maxRows - lastRow);
To:
if (lastRow < maxRows) {
sheet.deleteRows(lastRow + 1, maxRows - lastRow);
}
References:
getMaxRows()
getLastRow()
If I misunderstood your question and this was not the result you want, I apologize.

Google Sheets conditional combine, clear all cells in range, Loop. VBA to JS

I'm translating VBA into a Google Sheets script, but am a loss on how to get past this loop through all rows in a single column hurdle.
I have a column which contains an #OrdernNumber, a -State code, a First name, and sometimes a Last name, followed by an empty cell. I need to combine the First and Last names into the First name cell, and then clear the Last name cell below. This is illustrated in the screenshot below:
Column C and D in the screenshot reveal the pattern I used to construct my original VBA code:
Sub WorkingCombineAndClearLoop()
Dim Rngcell As Range
For Each Rngcell In Range("B1:B100")
'if first character is #
If left(Rngcell.Value, 1) <> "#" _
'and if first character is -
And left(Rngcell.Value, 1) <> "-" _
'and if cell is not blank then
And Rngcell.Value <> "" Then
'combine left cell
Rngcell.Value = Rngcell.Offset(0, -1).Value _
'with bottom left cell
& " " & Rngcell.Offset(1, -1).Value
'then clear below cell
Rngcell.Offset(1, 0).ClearContents
End If
Next
End Sub
Following is my annotated JavaScript code. Because I can't get the loop working yet, I've had to change the code logic a bit:
function workingCombineAndClearNoLoop() {
//get active spreadsheet, sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
//determine row count
var numRows = SpreadsheetApp.getActiveSheet().getRange("A:A").getLastRow();
//if A1's first character is not # AND
if (s.getRange('A1').getValue().substring(0,1) === "#" &&
//if A2's first character is not ' AND
s.getRange('A1').offset(1, 0).getValue().substring(0,1) === "-" ) {
//Set the value of A2 to...
s.getRange('A1').offset(2, 1).setValue(
//A2 cell content + " " + A2 cell content (concatenate)
s.getRange('A1').offset(2, 1).getValue() + " " +
s.getRange('A1').offset(3, 1).getValue())
//and then clear A2
s.getRange('A1').offset(3, 1).clearContent()
};
}
The following is where everything falls apart - the loop. I've only left the bare bones in this final copy of the code:
function notWorkingCombineAndClearWithLoop() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var numRows = SpreadsheetApp.getActiveSheet().getRange("A:A").getLastRow();
//loop
//how to replace 'A1' with A[i]?, so that it loops through all cells in the column?
for (var i = 0; i < 25; i++) {
if (s.getRange('A1').getValue().substring(0,1) === "#" &&
s.getRange('A1').offset(1, 0).getValue().substring(0,1) === "-" ) {
s.getRange('A1').offset(2, 1).setValue(
s.getRange('A1').offset(2, 1).getValue() + " " +
s.getRange('A1').offset(3, 1).getValue())
s.getRange('A1').offset(3, 1).clearContent()
}
};
}
Note: I've completed Codacademy's JS course and several Google Sheets tutorials, and am pretty good at VBA, but I'm still swimming in circles with this. I've tried to follow many loop examples, using variables in place of A1. Because I can't find any working solution, I've left the non-functioning A1 as a simple placeholder.
Short answer
//how to replace 'A1' with A[i]?, so that it loops through all cells
//in the column?
Use
'A' + i
Explanation
getRange() has several forms, one of them use A1 notation reference. This is used in concordance with the Google Apps Script included in the question.
Example
The main intention of below code is to show how to use the A1 notation to loop through the cells of a column in Google Apps Script.
There was some errors in the indexes. They were corrected.
Also changed the fourth line to reduce the rows to loop through and added a couple of lines.
function editedNotWorkingCombineAndClearWithLoop() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var numRows = SpreadsheetApp.getActiveSheet().getLastRow();
/*
* use variables to reduce number of calls to Apps Script services
* and improve readability
*/
var range, substring1, substring2;
//loop
for (var i = 1; i <= numRows; i++) {
//Here is the magic
range = s.getRange('A' + i );
substring1 = range.getValue().substring(0,1);
substring2 = range.offset(1, 0).getValue().substring(0,1);
if ( substring1 === '#' && substring2 === '-' ) {
range.offset(0, 1).setValue(range.getValue());
range.offset(1, 1).setValue(range.offset(1, 0).getValue());
range.offset(2, 1).setValue(
range.offset(2, 0).getValue()
+ " "
+ range.offset(3, 0).getValue()
)
}
}
}

Categories