Hide rows based on multiple checkbox values - google-apps-script

The project I am working on is to calculate costs of remaining sets in a mobile game. It has a spreadsheet with a list of all the sets, and checkboxes for all 5 pieces, Columns B:F. I want to include the option to hide all sets that are completed, so all Checkboxes are checked. This is done via another Checkbox, H16.
I have modified the Checkbox values to use Yes and No.
I have never used Google Apps Script before, and am very new to coding in general. I think what I need is, to use onEdit, then every time a cell is edited, check if H16 is TRUE, then scan through each row to check the B:F values. If all are true, hide that row. I don't know the best way to type that out, though.
Bonus points, I also want to include a reset checkbox, so when checked, set all values in B:F to false, and show the rows.
Here is a link to the spreadsheet
EDIT: My current GAS code, which isn't much because I don't know what I am doing:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var maxSheet = 100;
if(H16 == true)
{
for(i, i<=maxSheet, i = i + 1) {
}
} else {
sheet.showRows(1, maxSheet);
}
}

Hiding rows when all five columns are true
This may not be exactly what you wish but I think it's close. I did not use yes and no values because it's easier for me to leave it true false but you can change that. I'm using Sheet0 and you can change that as well. I used less rows so you can also change that. But the basic idea is that when H16 is clicked it hides rows that have all five columns checked.
Code:
function onEdit(e) {
e.source.toast('entry');//debug
const sh = e.range.getSheet();
const sr = 2;//data start row
const lr = 15;//last row of data
sh.getRange('K1').setValue(JSON.stringify(e));//debug
if(sh.getName() == "Sheet0" && e.range.columnStart == 8 && e.range.rowStart == 16 & e.value == "TRUE" ) {
e.source.toast('past if');//debug
e.range.setValue("FALSE");
let vs = sh.getRange(sr,2,lr - sr + 1, 5).getValues();
vs.forEach((r,i) => {
if(r[0] && r[1] && r[2] && r[3] && r[4]) {
e.source.toast(`Row: ${sr + i}`);//debug
sh.hideRows(sr + i);
}
});
}
}
Image of Sheet0:
I use K1 to provide me with the event object while I debug the script. And I also use the e.source.toast in several location to get an idea of what is going on.
Animation:
an incomplete description of the event object
You can get a better understanding of the event object by using the JSON.stringify code as show in my example.
Most new people want to run the code from the script editor so I'll tell upfront that unless you provide the event object to populate the e then it's not going to work. Just copy and past it and get you unique stuff set like sheet name and data space and then proceed to edit the page and figure out how it works.

Related

Google Apps Script function to change cell color based on current time vs the time typed in that cell

I have been struggling to make a consistent function that can do this. I have a column on a sheet that contains either blank or a time (in 24:00 notation), that is used to record when the data was last updated by the user. I would like for that cell to change colors when 2 hours have elapsed between the cell's content and the actual current time. The purpose is to notify myself that an update needs to made.
Conditional formatting fails to complete this reliably. Copy/pasting rows overwrites CF, and then leaves gaps in the range that the CF is applied to.
Example Sheet
Using Google's conditional formatting works, but not consistently. The CF formula I use is:
=TIMEVALUE(now()-time(2,0,0))>TIMEVALUE(C1)
applied to the whole column
I have made a poor attempt to do this using another script that changes cell colors, along with a trigger, and am failing. I can't figure out the most efficient way to read and edit the data and not sure how to integrate the time. How do I solve this and what is the best way to approach this problem?
Failed attempt & not sure how to proceed or restart:
function checkTime(e) {
var sheet = e.source.getActiveSheet();
// do nothing if not column 3
if (e.range.getColumn() !== 3) return
if (sheet.getName() !== "Stocks") return
const mainsheet = sheet.getSheetByName("Stocks")
const times = mainsheet.getRange("C2:C").getValues()
// not sure how to make this if-statement
// set the cell color
e.range.setBackground("yellow")
}
Edit: So for clarification, I dont need the time entered automatically, nor for this to be run OnEdit. A 15min trigger would suffice. Basically all I am trying to do is check column X for times greater than 2 hr old and highlight them yellow. If not greater than 2h old, or blank, do nothing or if already yellow, return to the color of other cells in that same row.
Update column3 when columns 1 or 2 change and highlight column 3 that are older than 2 hours. If edits occurs more often than every two hours then this should work. Other wise you may have to call a separate polling function with a timebased trigger.
function onEdit(e) {
//e.source.toast("Entry");
const sh = e.range.getSheet();
if (sh.getName() == "Sheet0" && e.range.columnStart < 3 && e.range.rowStart > 2) {
//e.source.toast("Gate1")
let hr = 3600000;
let dtv = new Date().valueOf();
sh.getRange(e.range.rowStart,3).setValue(new Date()).setNumberFormat("HH:mm");
let bg = sh.getRange(3,3,sh.getLastRow() - 2).getValues().flat().map((el => {
if((dtv - new Date(el).valueOf()) > 2 * hr) {
return ["#ffff00"];//hilite
} else {
//return ["#ffffff"];//default bg
//Logger.log(e.range.offset(0, -1).getBackground());
return [e.range.offset(0, -1).getBackground()];//alternative solution
}
}));
if(bg && bg.length > 0) {
//e.source.toast("Gate2");
sh.getRange(3,3,bg.length, bg[0].length).setBackgrounds(bg);
}
}
}
Demo:
Changed the 2 hour threshold to a smaller time to work faster for the demo.
Here's a timebased update script:
function update() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
let hr = 3600000;
let dtv = new Date().valueOf();
let bg = sh.getRange(3, 3, sh.getLastRow() - 2).getValues().flat().map((el => {
if ((dtv - new Date(el).valueOf()) > 2 * hr) {
return ["#ffff00"];//hilite
} else {
return [e.range.offset(0, -1).getBackground()];//alternative solution
}
}));
if (bg && bg.length > 0) {
sh.getRange(3, 3, bg.length, bg[0].length).setBackgrounds(bg);
}
}
Run this to create update trigger or create it manually.
function createUpdateTrigger() {
if(ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction("update")).length == 0) {
ScriptApp.newTrigger("update").timeBased().everyMinutes(5).create();
}
}
I don't love the solution from #Cooper since it doesn't apply to the general case if you're not updating the sheet every two hours. Even if you are, in our worst case scenario, you update the sheet 1 hour and 59 minutes after a cell was last updated; and then not again for another 1 hour and 59 minutes, in which case the flip to yellow is 1 hour and 58 minutes delayed. I would suggest also putting this function in a trigger--any less frequent than once every 5 minutes should not run into a max trigger executions error (but my suggestion is usually every 15 minutes). This way, you keep your data fairly fresh no matter what, and you can also have it update when edits are made if you want to.

Checkboxes as counters in multiple columns and sheets (but not al)

Here is the sample sheet
What I need to do is make the checkboxes in the camera and check-in columns (green) work as a counter. When you click on checkbox, it should add one to the total of the column next to it. The trouble is that this has to work in all the sheets, but not in all the columns (I use checkboxes for other things in other columns). And the columns where that kind of data rests isnt exactly the same for all sheets.
How do I make it so that the ADD + 1 button works in a sheets, but only in the columns that I want?
Thanks for the help. Feel free to edit sheet and put code directly on it if you are willing to help.
You can do something like this:
function onEdit(e) {
const sh = e.range.getSheet();
const shn = ["Sheet0","Sheet1","Sheet2"];//you pick the sheets
const cols = [1,5,1];//you pick the columns
const idx = shn.indexOf(sh.getName());
if(~idx && e.range.columnStart == cols[idx] && e.value == "TRUE") {
e.range.offset(0,1).setValue(e.range.offset(0,1).getValue() + 1);
e.range.setValue("FALSE");//Reset the checkbox back to false so it behaves more like a switch
}
}

Ticking checkbox triggers function to add time stamp

I'm building a spreadsheet that tracks specific data. I want to place a timestamp in the corresponding adjacent cell when I tick a checkbox - alternative I would be happy with a note on the checkbox stating the time it was last ticked TRUE.
I have literally spent an entire day searching everything on Stackoverflow and google, and I have tried many different methods and functions. Primarily I have been using the onEdit(e) function to (try and) add the time stamp. Early attempts identified a known flaw that meant onEdit(e) wasn't capturing all instances of the checkbox change state. I changed my approach accordingly, both attempting to add the timestamp to the corresponding adjacent cell, and also add a note to the checkbox cell - both seemed to work sporadically which was extremely frustrating.
I have three primary issues with this problem at the moment:
Knowing the syntax for checking the value of the checkbox (do I use ==, or ===, and do I use '', ", or no quotation marks?). That sounds like a very stupid question, but I have learning difficulties and have significant trouble retaining written information in my memory :(
Successfully setting an `if' -> 'else if' statement that will "toggle" the timestamp on or off depending on the state of the checkbox.
I would prefer to set a note with the timestamp on the checkbox cell that is cleared when the checkbox is unticked - however the few times I did get it to work the timezone was incorrect. I did a lot of reading about timezone but without a working example I was unable to comprehend how to implement it into my code. I'm +10 GMT
I have provided a cut down version of my spreadsheet containing only the specific sheet & code I am having trouble with:
https://docs.google.com/spreadsheets/d/1NCdMziBpj0joSv9lQfqT9etz9hMsvgMeuT8X9XTxR20/edit?usp=sharing
Here is the latest iteration of code I have been working on. I've probably attempted half a dozen completely different functions but have deleted each one after several hours of hitting brick walls:
function onEdit() {
var ss = SpreadsheetApp.getActiveSheet();
if(ss.getName() == "Quests" ) { //checks that we're on the correct sheet
var tickboxCell = s.getActiveCell();
if(tickboxCell.getColumn() == 3 && tickboxCell.getValue() === 'TRUE' ) { //checks the status of the tickbox
var dateCell = tickboxCell.offset(0, 1);
dateCell.setValue(new Date());
}
}
}
Here is the sample code I modified for adding a note to the checkbox:
function setNote(note){
var note = note;
var cell = SpreadsheetApp.getActiveSheet().getActiveCell();
if(note == ""){
cell.clearNote();
}else{
cell.setNote(note);
}
}
I would expect that any time a checkbox is ticked, either the corresponding cell to the right would input date(), or alternatively the checkbox would add a note stating "Quest Completed: dd/mm/yyy" (+10 GMT timezone). I would then expect the timestamp cell to clear, or the note to be cleared if the checkbox state is unticked.
try this:
function onEdit(e) {
if(e.range.getSheet().getName() != 'Quests') { return; }
if(e.range.columnStart==3 && e.value=="TRUE") {
e.range.offset(0,1).setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd:HHmm"));
}
}
Thanks to Cooper who put me on the right path with his code. It didn't do exactly what I was looking for, but it enabled me to make a few minor changes to it to get exactly what I was looking for.
EDIT: I have found the solution for setNote as well which I will provide here.
Code for adding/removing date to adjacent cell:
function onEdit(e) {
if (e.range.getSheet().getName() != 'Quests') { return; }
if (e.range.columnStart==3 && e.value=="TRUE") {
e.range.offset(0,1).setValue(new Date());
}
else if (e.range.columnStart==3 && e.value=="FALSE") {
e.range.offset(0,1).clearContent();
}
}
Code for adding/removing setNote from tickbox with correct timezone:
if(e.range.getSheet().getName() != 'Quests') { return; }
if(e.range.columnStart==3 && e.value=="TRUE") {
e.range.setNote('Completed: ' + Utilities.formatDate(new Date(), SpreadsheetApp.getActive().getSpreadsheetTimeZone(), "dd-MM-yy HH:mm:ss"));
}
else if (e.range.columnStart==3 && e.value=="FALSE") {
e.range.clearNote();
}

'onEdit' function doesn't always fire when editing multiple cells quickly

Purpose
This is for a checklist.
People type 'x' into a cell. If x is the only thing in the cell, it should change to ✓
Script
function onEdit(e) {
var r = e.range;
if (r.getValue() === 'x' || r.getValue() === 'X') {
r.setValue('✓');
r.setHorizontalAlignment('center');
}
}
Problem
Going slowly works.
But when inputting quickly (ie: type 'x', move to another cell with the arrow keys, type 'x', move, [quickly repeat this multiple times]), some cells change, but some remain as x.
EDIT:
I now have a working (but inelegant) solution. I'd still like to know if there's a more elegant solution.
This works
// Checks 300 rows and 11 columns, starting from E10 (row 10, col 5)
// if the value is 'x' or 'X', it changes it to a '✓'
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
// getRange(row, column, numRows, numColumns)
var values = sheet.getRange(10, 5, 300, 11).getValues();
for (var i=0; i<values.length; i++) {
for (var j=0; j<values[i].length; j++) {
if ( values[i][j] === 'x' || values[i][j] === 'X') {
var cell = sheet.getRange( 10 + i, 5 + j );
cell.setValue('✓');
cell.setHorizontalAlignment('center');
}
}
}
}
This checks every cell in the tables (3300 of them) each time onEdit fires. Since onEdit seems to always catch the last edit (but not the ones in between), this changes every x to ✓.
Background
At work, I've been asked to convert a checklist into a Google Doc. There are hundreds of checkboxes in various tables.
I've been asked to make the boxes have checks, ✓, since we print and show this checklist to clients.
Many of my colleagues are not computer literate, so the input method has to be very simple.
Questions
Is there a way to fix the original script, or is there another better way to do this?
I don't know if having data validation is an option? Else you can do this;
type in a cell: =char(10003) which will output as ✓
create data validation and choose list with items
copy the symbol (not the formula) and paste it in the box (and put a comma behind it)
if you need another symbol for not completed, repeat the previous steps with =char(10005), which will output as ✕
Besides that you can maybe use the square root symbol, which on my mac is easily entered using option-shift-V and looks like √ (you will have to look up that combination for your OS).

deleteRow based off cell edit on another sheet of same spreadsheet

WHAT I HAVE One google spreadsheet named "Script Test" with two sheets named "delete" and "non delete".
WHAT I NEED If a row in Col B on "non delete" is changed to 'DELETE' via the drop down menu, the row with the same Buy Number on "delete" will be deleted.
WHAT I HAVE TRIED
What has worked = By researching on stack I found an onEdit function that deletes a row on "delete" based on if a cell has a specific value. In this case, that value is 'DELETE'. The problem with this is, I can only get it to work if that cell is on the sheet "delete" rather than the sheet "non delete". If I'm working off of "non delete" and need to go back to "delete" to delete a row of information, I can just right click on the row number and manually delete it. So, this script isn't necessarily saving me time.
This script is as follows:
function onEdit(e) {
try {
var ss = e.source;
var s = ss.getActiveSheet();
if (s.getName() == 'delete' &&
e.range.columnStart == 1 && e.range.columnEnd == 1 && // only look at edits happening in col A which is 1
e.range.rowStart == e.range.rowEnd ) { // only look at single row edits which will equal a single cell
checkCellValue(e);
}
} catch (error) { Logger.log(error); }
};
function checkCellValue(e) {
if (e.value == 'DELETE') {
e.source.getActiveSheet().deleteRow(e.range.rowStart);
}
}
What has not worked = I fiddled with the script a bit to have it read Col F on "delete" and in Col F I have an Index Match of Col B in "non delete". However, this does not delete the row on "delete" when Col F changes to 'DELETE'. Now I'm not 100% on this but I can pretty much deduce that this is happening because Col F isn't being "edited", rather the formula inside of it is "updating". I've also tried fiddling with other scripts that I found on stack but none seem to have gotten me as close as the script above.
THINGS TO THINK ABOUT
First of all, thanks for any help you guys can give me. Just before posting this question, I came across a filter function that I think may be a direction to head in if I'm right about the Index Match. I found one function that hides rows based on a filter but I would need the row to be deleted so I'm assuming that is as simple as switching hideRows with deleteRows. I've tried adding screenshots of what I need done but I don't have enough reputation. I can and will add a link to a copy of the spreadsheet if that helps. Once again, thanks for any tips or guidance.
Copy of Script Test
Use the getSheetByName() method to get the delete sheet.
function checkCellValue(argRowToDelete) {
if (e.value == 'DELETE') {
var toDeltSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("delete");
toDeltSheet.deleteRow(argRowToDelete);
}
}
If you want a separate function just for deleting the row, maybe do the check for the 'DELETE' text in the main function, and pass the row number to the delete function:
I've tested the following code, and it works. When the drop down list is used to select 'DELETE' in the 'non delete' sheet, it deletes the corresponding row in the 'delete' sheet.
I made multiple changes to the code. Even though this code deletes a row in a sheet different from where the edit is taking place, there is still a potential problem. Once a row in the 'delete' sheet is deleted, the rows will shift. If you start deleting rows at the top or middle, then every row below the deleted row is no longer synced with the rows in the 'delete' sheet.
So, this answers your question, but now you have yet another problem.
function onEdit(e) {
try {
var ss = e.source;
var s = ss.getActiveSheet();
var colStart = e.range.columnStart;
var colEnd = e.range.columnEnd;
Logger.log('colStart: ' + colStart);
Logger.log('colEnd: ' + colEnd);
var thisRow = e.range.getRow();
Logger.log('s: ' + s.getName());
//Avoid looking at multi column edits. If column start and column end is same column,
//then not a multi column edit
var editedFromNonDelete = (s.getName() === 'non delete');
Logger.log('editedFromNonDelete: ' + editedFromNonDelete);
var editedFromColB = (colEnd === 2) && (colStart === colEnd);
// only look at edits happening in col B
if (editedFromNonDelete && editedFromColB) {
Logger.log('e.value: ' + e.value);
if (e.value === 'DELETE') {
fncDeleteRow(thisRow);
};
}
} catch (error) {
Logger.log(error);
}
};
function fncDeleteRow(argRowToDelete) {
var toDeltSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("delete");
toDeltSheet.deleteRow(argRowToDelete);
};
After testing out the filter function for a couple minutes, I've pretty much got it to do what I needed. Thanks anyways!