Search a row for matches - google-sheets

I'm very new to Google Sheets and particularly using the Apps Script API to make functions for Sheets.
My goal is to search 3 cells in a row, and if two of three contain an 'X', the third which does not turns blue.
Currently the sheet has conditional formatting as follows:
X = Green
Empty = Red
? = Orange
! = Blue
So the intent is to change the Empty cell to ! if the other two cells in the row are X.
Image for reference:
Essentially, I need a function which can check a range of cells for their contents, but I don't know how to properly use the API, if someone could give me a bit of help it would be greatly appreciated.
Note: This is NOT for a project of any sort, this is just for my friends and myself.
Edit
My thoughts were to have the range as the function parameter (A1:C1 for instance) and then access the cell's data for the range and check them against each other. My issue is that I don't know how to use the API to get this data.

If the scenario mentioned before applies, you can use this method:
function checkRow() {
var ss = SpreadsheetApp.getActive();
var activeCell = ss.getActiveCell();
var currentRow = activeCell.getRow();
var currentCol = activeCell.getColumn();
var allRange = ss.getRange("A1:C3");
var activeValue = activeCell.getValue();
var secondCell, thirdCell;
if(activeValue == "x")
{
if ( currentCol == 1 )
{
secondCell = allRange.getCell( currentRow, currentCol+1 );
thirdCell = allRange.getCell( currentRow, currentCol+2 );
} else if ( currentCol == 2 )
{
secondCell = allRange.getCell( currentRow, currentCol-1 );
thirdCell = allRange.getCell( currentRow, currentCol+1 );
}
else if ( currentCol == 3 )
{
secondCell = allRange.getCell( currentRow, currentCol-1 );
thirdCell = allRange.getCell( currentRow, currentCol-2 );
}
if ( secondCell.getValue() == "x" && thirdCell.getValue() == "" )
{
thirdCell.setValue("!");
thirdCell.setBackground("#00FFFF");
} else if (thirdCell.getValue() == "x" && secondCell.getValue() == "" )
{
secondCell.setValue("!");
secondCell.setBackground("#00FFFF");
}
}
}

Related

Selecting chapters, section and subsection with google sheets with drop down list

we have currently a large number of exercises (for math) that need to be categorized according to an existing table of contents. Each exercise has a unique number. This number should be placed into the table of contents. The depth of the toc is 3, so we have chapters, sections and subsections.
I want three drop down lists for each of the (many) exercises. The first one selects the chapter, the second the section, and the third the subsection. I can solve this with two filtered lists that depend on certain filtering of a list seperated into chapter, section, subsection. That works fine for few exercises. But, i have to make the filtered lists for each exercise seperately. That is exactly the problem. I do not want to maintain 500 lists (two per exercise)
Is there any way to hardcode this? I do not want to scroll through the many subsections each time and I need a error control. So, one should not be able to select chapter 3 and then subsection 3 from chapter 4.
EDIT: Link to Google Sheet:
https://docs.google.com/spreadsheets/d/e/2PACX-1vSsRqFlLIkpgIrw18GBDLdUEl0FFmF5hSXIe2oAXztz9N50VNiO0eCP3cAB20KOgHU4nfH6gBFCWmyT/pubhtml
1. Separate the data and the dropdows
That's a best practice, because you will need to have some auxiliary cells to create the dependency
2. Create a simple data validation dropdown
Do it for the first column in a separate sheet
3. Create the auxiliary cells
Filter the second column with the values you have in the data validation to show the values adjacent to the first column
=FILTER(B2:B,A2:A=Dropdowns!$A$2)
4. Repeat the process with as many columns you have
Here is the sample spreadsheet for you to have an idea how to implement it
https://docs.google.com/spreadsheets/d/1VPsx1hfKuZDifMgrREOvUb4xEIvbVODnKPuRDWZUOF8/edit?usp=sharing
Update
I'm working on a script, I created two functions, at the moment they're not achieving the step you want, but I'm thinking on looping the number of columns as the same way I'm doing it with the rows.
const menuSheet = 'Values';
const dataSheet = 'Data';
const wsValues = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(menuSheet);
const wsData = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dataSheet);
const data = wsData.getRange(2, 1, wsData.getLastRow() - 1, 3).getValues();
let firstCol = 1;
let secondCol = 2;
let thirdCol = 3;
/*
function setDropdownDependenciesTest() {
const list = ['a', 'b']
const cell = wsValues.getRange('C2')
setDropdownDependencies(list, cell)
}
*/
function onEdit(e) {
const activeCell = e.range
let val = activeCell.getValue()
let row = activeCell.getRow()
let column = activeCell.getColumn()
let wsName = activeCell.getSheet().getName()
if (wsName === menuSheet && column === firstCol && row > 1) {
applyFirstValidation(val, row)
} else if (wsName == menuSheet && column === secondCol && row > 1) {
applySecondValidation(val, row)
}
}
function applyFirstValidation(val, row) {
if (val === "") {
wsValues.getRange(row, secondCol).clearContent()
wsValues.getRange(row, secondCol).clearDataValidations()
wsValues.getRange(row, thirdCol).clearContent()
wsValues.getRange(row, thirdCol).clearDataValidations()
} else {
wsValues.getRange(row, secondCol).clearContent()
wsValues.getRange(row, secondCol).clearDataValidations()
wsValues.getRange(row, thirdCol).clearContent()
wsValues.getRange(row, thirdCol).clearDataValidations()
let filteredData = data.filter(info => {
return info[0] === val;
})
let listToApply = filteredData.map(info => {
return info[1]
})
let cell = wsValues.getRange(row, secondCol)
setDropdownDependencies(listToApply, cell)
}
}
function applySecondValidation(val, row) {
if (val === "") {
wsValues.getRange(row, thirdCol).clearContent()
wsValues.getRange(row, thirdCol).clearDataValidations()
} else {
wsValues.getRange(row, thirdCol).clearContent()
let firstColValue = wsValues.getRange(row, firstCol).getValue()
let filteredData = data.filter(info => {
return info[0] === firstColValue && info[1] === val;
})
let listToApply = filteredData.map(info => {
return info[2]
})
let cell = wsValues.getRange(row, thirdCol)
setDropdownDependencies(listToApply, cell)
}
}
function setDropdownDependencies(list, cell) {
const rule = SpreadsheetApp
.newDataValidation()
.requireValueInList(list)
.setAllowInvalid(false)
.build()
cell.setDataValidation(rule)
}

Can I Simplify my Script in Google Sheets?

I have built a Google Sheet using scrips to automatically timestamp certain events on the sheet upon edit.
Two of the timers are built to time just the first edit of two particular cells:
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "SUBARU" ) {
var r = s.getActiveCell();
if( r.getColumn() == 3 ) {
var nextCell = r.offset(0, 19);
if( nextCell.getValue() === '' )
nextCell.setValue(new Date());
}
}
}
Two additional timers time any instance two other cells are edited:
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "SUBARU" ) {
var r = s.getActiveCell();
if( r.getColumn() == 9 ) {
var nextCell = r.offset(0, 15);
nextCell.setValue(new Date());
}
}
}
And three other timers time when three specific entries happen in one particular cell:
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "SUBARU" ) {
var r = s.getActiveCell();
if( r.getColumn() == 5 ) {
var nextCell = r.offset(0, 20);
if( r.getValue() === "3 - Menu Ready")
nextCell.setValue(new Date()).setNumberFormat('dd-MM-yyyy HH:mm:ss');
}
}
}
My problem is that I have seven scripts per sheet, and 4 sheets using the scripts (so 28 total scripts running). My Google quote is 30 scripts running at the same time. Can anyone think of ways I can simplify these scripts to run quicker or combine any so that I have fewer running at any given time?
Any help would be greatly appreciated!!!
My suggestion for this is to group all logic into one "onEdit" function.
Here's my code approach/suggestion to handle it:
if(s.getName() == "SUBARU") {
//put the logic to handle sheet SUBARU
} else if(s.getName() == "ABC") {
//put the logic to handle sheet ABC
} else if(s.getName() == "DEF") {
//put the logic to handle sheet DEF
}

How can I color a row using jsPDF AutoTable based on a single cell value?

I'm trying to create a PDF based off of an interactive/dynamic table using jsPDF + AutoTable. The last cell in a row will either say "Yes" or "No" - if the cell says "Yes" the entire row should be highlighted.
The closest I've come is getting the cell to highlight, but I need the entire row to highlight. I seem to be having trouble tying the cell to the row index.
This is my code so far:
willDrawCell: function (data) {
if (data.cell.text == "Yes") {
doc.setFillColor(67, 160, 71);
}
}
Thanks for any help.
I was able to resolve this!
Before my doc.autoTable() call, I calculate the index where the "Yes" is, and store those values in an array. I then loop over each row index and setFillColor as appropriate. Here is what my code looks like now:
willDrawCell: function (data) {
if (data.cell.text == "Yes") {
doc.setFillColor(67, 160, 71);
}
for (let i = 0; i < ready_rows.length; i++) {
if (data.row.index === ready_rows[i]) {
doc.setFillColor(239, 154, 154);
}
}
}
here currentRow is row which given in auto table
var res1 = doc.autoTableHtmlToJson(document.getElementById(m));
var idmm=document.getElementById(n);
var clength=res1.columns.length;
var crow=res1.rows.length;
doc.autoTable(res1.columns, res1.data, {margin: {top: vy+25},pageBreak: 'auto',styles: {cellPadding: 1.5,fontSize:fsize , },fontStyle: 'bold',drawRow: function (row, data) {
currentRow1 = row;
currentRow1.height =30;
if((currentRow1.cells[0].text[0].includes('Total')) || (currentRow1.cells[0].text[0].includes('Avg'))||(currentRow1.cells[0].text[0].includes('count'))||(currentRow1.cells[0].text[0].includes('Min'))||(currentRow1.cells[0].text[0].includes('Max')))
{
1
for(var i=0;i<res1.columns.length;i++)
{
currentRow1.cells[i].styles.fontStyle = "bold";
currentRow1.cells[i].styles.font = "sans-serif" ;
currentRow1.cells[i].styles.fillColor = [243,205,204];
currentRow1.cells[1].styles.columnWidth="wrap";
currentRow1.cells[1].styles.FontSize=30;
}
}
},columnStyles: {0: {columnWidth: columnwidthrgroup},},});
```

Conditional Formatting to a specific column

I'm new to google sheets and I like to set conditional formatting to a specific column.
Starting on Row 8, Column D
I want to set the back ground color based on that is on the cell
If cell content like 'pass' then background color should be green (183,225,205)
If cell content like 'fail' then background color should be Red (213,139,139)
If cell content like 'pending' then background color should be Yellow (252,232,178)
So on edit of the cell I want to check the value of the cell
function onEdit(e){
var actualSheetName =
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName()
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var allValuesOnColumA = ss.getRange("A1:A").getValues()
var lastRow = allValuesOnColumA.filter(String).length + 1
var range = ss.getRange(8, lastRow)
conditionalFormatting(8, 4, (lastRow +1) -8, 1, actualSheetName)
}
I started a conditionalFormatting Function but I can't get it to work.
function conditionalFormatting(rowN, colN, optRows, optCols, spreadsheetName)
{
var mySpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var mySheet = mySpreadsheet.getSheetByName(spreadsheetName)
var range1Values = mySheet.getRange(rowN,colN, optRows, optCols).getValues()
//debugger
for (var row in range1Values)
{
for (var col in range1Values[row])
{
for(var i=0, iLen=range1Values.length; i<iLen; i++)
{
if(range1Values[row][col] == 'pass')
{
//debugger
Logger.log('Local Row ' + locRow + ' Local Col ' + locCol)
var pendingCell=mySheet.getRange(locRow, locCol)
pendingCell.setBackground("green")
}
else if(range1Values[row][col] == 'fail')
{
// Logger.log('fail in')
//mySheet.getRange(range1Values.offset(row, col, 1, 1).getA1Notation()).setBackgroundColor(213,139,139) //red
//Logger.log('fail Out')
}
else if(range1Values[row][col] == 'pending')
{
//Logger.log('Pending in')
//mySheet.getRange(range1Values.offset(row, col, 1, 1).getA1Notation()).setBackgroundColor(252,232,178) //Yellow
//Logger.log('Pending Out')
}
}
}
}
}
Edit:
The desired result should look like:
Try this (works on cells that have the keywords 'pass', 'fail', and 'pending' starting in row 8 for column D as specified):
function onEdit(e){
var actualSheetName = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName();
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow = ss.getLastRow();
conditionalFormatting(8, 4, (lastRow +1) - 8, 1, actualSheetName);
}
function conditionalFormatting(rowN, colN, optRows, optCols, spreadsheetName) {
var mySpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var mySheet = mySpreadsheet.getSheetByName(spreadsheetName);
var range1Values = mySheet.getRange(rowN,colN, optRows, optCols).getValues();
for (var row in range1Values) {
for (var col in range1Values[row]) {
if (range1Values[row][col].toLowerCase() == 'pass' || range1Values[row][col].toLowerCase() == 'passed') {
mySheet.getRange(Number(row)+rowN, Number(col)+colN).setBackground('green')
Logger.log('Contents PASS')
} else if (range1Values[row][col].toLowerCase() == 'fail' || range1Values[row][col].toLowerCase() == 'failed') {
mySheet.getRange(Number(row)+rowN, Number(col)+colN).setBackgroundRGB(213,139,139)
Logger.log('Contents FAIL')
} else if (range1Values[row][col].toLowerCase() == 'pending') {
mySheet.getRange(Number(row)+rowN, Number(col)+colN).setBackgroundRGB(252,232,178)
Logger.log('Contents PENDING')
} else {
Logger.log('Contents did not meet any of the criteria')
}
}
}
}
Out of curiosity, is there any reason you aren't just using the built-in conditional formatting feature in Google Sheets? Although this code works, I find it much easier to just use the built-in conditional formatting feature.
EDIT (FOR ADDITIONAL QUESTION IN COMMENTS):
Modified code to not be case sensitive with keywords and also included keywords 'passed' & 'failed'
Hope this helps!

moveTo messing up cells outside the range

im rearranging a spreadsheet using google script. I am first sorting it and then moving some rows within a range to the end of the sheet. Everytime i do the moveTo function some cells that reference the moved rows get changed to reflect the new row numbers even though the cells are outside my range and should not be modified. For example if im moving cell b3 and i have cell f4 =b3 then when i move b3 cell f4 changes to be whatever b3 is now. i tried locking it with =b$3 but still didnt work. Also it messes up the conditional formatting that should be in place for the entire column using something like "d2:e" and it changes to be something like "d2:e109" or something similar. Any clue whats going on?
function onEdit(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var allowedSheet = 1;
if(sheet.getIndex() == allowedSheet) {
var editedCell = sheet.getActiveCell();
var sortBy = [1, 3, 2];
var triggerCol = [1,2,3,10,11,12];
var rangeStart = "A";
var rangeEnd = "E";
var tableRange = "A2:E";
if(triggerCol.indexOf(editedCell.getColumn()) > -1) {
var range = sheet.getRange(tableRange);
range.sort([{column: sortBy[0], ascending: true}, {column: sortBy[1], ascending: false}, {column: sortBy[2], ascending: true}]);
var indexOfIncome = find( sheet, 2, "Income");
if( indexOfIncome > 0 ){
var overflowRange = sheet.getRange("A2:E" + (indexOfIncome - 1 ));
var lastRow = findFirstEmptyCell( sheet, 1 );
overflowRange.moveTo(sheet.getRange("A" + ( lastRow )));
var fullRange = sheet.getRange(rangeStart + indexOfIncome + ":" + rangeEnd);
fullRange.moveTo(sheet.getRange(rangeStart + "2"));
}
}
}
}
function find( sheet, column, value ) {
var data = sheet.getRange(1, column, sheet.getMaxRows()).getValues();
for( i = 0; i < sheet.getMaxRows(); i++ ){
if (data[i][0].toString().indexOf(value) > -1 ) {
return i + 1;
}
}
return 0;
}
function findFirstEmptyCell ( sheet, column ){
var data = sheet.getRange( 1, column, sheet.getMaxRows() ).getValues();
for( i = 0; i < sheet.getMaxRows() ; i++ ){
if( data[i][0] == "" ){
return i + 1;
}
}
return 0;
}
It's an expected behaviour, moveTo works just like Cut (Ctrl+X), while your looking for Copy+Paste then delete original content, which should be copyTo(newRange) associated with clear(oldRange).
I got it to work by using copy and then clearing. Now why in the world google would make a single function that behaves the exact opposite from all the other functions is beyond me. The documentation says moveto behaves as cut and paste but when i cut and paste i do not mess up the rest of my sheet.

Resources