Ticking checkbox triggers function to add time stamp - google-apps-script

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();
}

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.

Hide rows based on multiple checkbox values

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.

How to update the timstamp while choosing the same item in drop-down list in google sheet?

Hi everyone,
I have a script where it is able to print the current timestamp in Cell C8 when I choose Transfer in cell B8. However, when I click Transfer in the drop-down list for the second time, the timestamp will not update since the word is still the same. My code is is attached below:
function onEdit(e){
const dt = Utilities.formatDate(new Date(), 'Asia/Singapore', 'HH:mm:ss');
if (e.range.columnStart == 2 && e.range.rowStart >= 8) {
if (e.value == 'Transfer') {
e.range.offset(0, 1).setValue(dt);
} else {
e.range.offset(0, 1).setValue('');
}
}
}
I understand that cell B8 need to change so that OnEdit function will change and print the current timestamp. I also know that I can include Transfer 1, Transfer 2 in my drop-down list and my script but this is not an ideal way. Is there any way to change the timestamp even though I'm still choosing Transfer in the drop-down list? Possible other type of function instead of OnEdit?
Any help will be greatly appreciated!
Why not just add a clear of B8 before the }else{ in the script?
i.e., after:
e.range.offset(0, 1).setValue(dt);
... add this line (indented to the same level as the above line for clarity)...
e.range.clearContent();

Script to Change Row Color with a input from a Google Form

Basic conditioning formatting issue, (or so I thought); our spreadsheet acts as a Daily log, therefore, besides the top row (frozen titles), everything gets manually moved to an archive on a daily basis. This renders the innate Conditional Formatting function inert as it not longer works after the data move.
So I found an modified a script until it works exactly the way we need it to, with one minor issue, all input is gathered thru the use of a Google Form, and even though the script works when the value in question is manually entered it does not function when entered thru the form, I tried using a trigger but to no avail.
When the form is filled and submitted, a Radio Button (if selected), enters ME? onto column E, if that value is entered then A2:E gets a change in the font color and cell background color, otherwise is business as usual. This is the script as it currently stands:
function OnEdit(e) {
if (e) {
var ss = e.source.getActiveSheet();
var r = e.source.getActiveRange();
if (r.getRow() != 1 && ss.getName() == "Form Responses 0") {
ME = ss.getRange(r.getRow(), 5).getValue();
rowRange = ss.getRange(r.getRow(),1,1,5);
// This changes font and row color
if (ME == 'ME?') {
rowRange.setFontColor("#056afa");
rowRange.setBackground("#fae7bf");
// DEFAULT
} else if (ME == ' ') {
rowRange.setFontColor("#000000");
rowRange.setBackground("#ffffff");
}
}
}
Any Help would be greatly appreciated...
Since this trigger must be activated from an event, not from a user action, you shall create an installable trigger using the On form submit type. First, I strongly recommend changing your function name from OnEdit(e) (that looks very similar to the reserved trigger onEdit(e)) to some descriptive name (I used colorUpdater(e)). You can add the installable trigger from the App Script editor, using the menu Edit 🠊 Current project's triggers. There you must click + Add Trigger and select the following parameters:
Choose which function to run: colorUpdater
Choose which deployment should run: Head
Select event source: From spreadsheet
Select event type: On form submit
Failure notification settings: as you wish
You can find more information about this process on the managing triggers manually documentation. Then you will need to update your function to look like the following code. It is your original function with some little changes that I made to implement the new approach.
function colourUpdater(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var r = e.range;
if (r.getRow() != 1 && ss.getName() == "Form Responses 0") {
var rowRange = ss.getRange(r.getRow(), 5, 1, 1);
var ME = rowRange.getValue();
if (ME == 'ME?') {
// This changes font and row color
rowRange.setFontColor("#056afa");
rowRange.setBackground("#fae7bf");
} else {
// DEFAULT
rowRange.setFontColor("#000000");
rowRange.setBackground("#ffffff");
}
}
}
Please, ask me for clarification if you need more information or this approach doesn't fulfills your original request.

Google sheets script writing, how do I prevent an auto update or recacluation of my timestamps in googel sheets?

I attached a copy of the spreadsheet I run in google. The concept is when someone begins picking an order, they will for instance type their name in I3 and their time will start. once complete they will type their name in J3 , each causing a time stamp below leading to a total duration time. later it will factor percentages.
The problem is the time stamps seem to randomly update without prompting to do so. it seems to be when it is printed or reopened. This will cause inaccuracies in the times and percentages. Any assistance would be greatly appreciated
It would appear that I should write a script to accommodate this need, but I haven't the slightest on how to do this. I was directed to this forum from a reply in google docs help forum
enter link description here
I don't think you can choose on which cell you update or recalculate (Tell me if I'm wrong).
A non-optimized workaround (Maybe some expert have got better solution):
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var i = 3;
while(i <= sheet.getLastRow())
{
var data = sheet.getRange(i, 9, 3, 2).getValues();
if(data[1][0].length == 0)
{
if(data[0][0] != "")
{
var d = new Date();
sheet.getRange(i+1, 9).setValue(d);
}
}
if(data[1][1].length == 0)
{
if(data[0][1] != "")
{
var d = new Date()
sheet.getRange(i+1, 10).setValue(d);
}
}
i=i+3;
}
}
The onEdit(e) function is a simple trigger implement in Google Spreadsheet. It will trigger each time a cell is edit.
On this code above, we iterate to check on each element if a name was added or not. If so, the code will set the date and time below the name just added. Since you set the value on one single moment, no auto update will modify those time.
for this workaround, you need to remove the formula you put on the cell where you want your timestamp (i.e I4 and J4). You can keep the cells which calculate the difference between time, they should work with no problem.
Paste below script in the script editor and save it.
function onEdit(e) {
if (e.range.rowStart % 3 != 0 || [9, 10].indexOf(e.range.columnStart) == -1) return;
var o = e.range.offset(1, 0);
if (!o.getValue()) o.setValue(new Date())
}
Don't run the script by clicking the play button in the script editor. Instead to back to any of the tabs, clear all cells where you now have the =now() formula and enter a name. See if the timestamp appears.