Time/Date-Based Rule for Conditional Formatting via Google Apps Scripting - google-apps-script

Alright, I'm stuck.
I've got a sheet that an entire team of people use to communicate with each other and sign up for timeslots for internal logins.
I've got the sheet formatting performing the way I want to, but, it's too easily edited by people, because they have to be able to edit the values in the cells.
It's currently running Conditional Formatting rules, based upon time/date. Basically, if the cell's timeslot has passed, then it's blacked out. It is also blacked out if the cell isn't associated with the current day.
https://docs.google.com/spreadsheets/d/1TP5iJ9AA_xqDQPiyl89ntZehd7aqDPELcN0bRMGP-SU/edit?usp=sharing
I'm trying to create a script that "onEdit" will restore any formatting (not the value) that had been changed during the edit.
Got it this far, but I'm struggling to make it conditional like the UI rules allow me to do.
function myFunction() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var range1 = sheet.getRange('A1:B10');
range1.mergeAcross()
range1.setBackground('white');
range1.setBorder(true,true,true,true,true,true);
range1.setFontColor('black');
range1.setFontFamily('Arial');
range1.setFontSize(10);
range1.setFontWeight("normal");
range1.setFontStyle("normal");
range1.setHorizontalAlignment("left");
range1.setVerticalAlignment("center");
range1.setWrap(false);
}
Here's where I'm struggling (notes inserted)
function myFunction() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var range1 = sheet.getRange('A1:B10'); // I'd have this repeated for multiple ranges
range1.mergeAcross()
range1.setBackground('white'); // Need an "IF" argument for this, such as if today is Wednesday and time is before 2:30pm, then white, otherwise black. Could probably put this at a different point in the script.
range1.setBorder(true,true,true,true,true,true); // All the following attributes would simply restore defaults
range1.setFontColor('black');
range1.setFontFamily('Arial');
range1.setFontSize(10);
range1.setFontWeight("normal");
range1.setFontStyle("normal");
range1.setHorizontalAlignment("left");
range1.setVerticalAlignment("center");
range1.setWrap(false);
}
Thanks in advance for any help you might offer!
Adam

I've actually been trying to deal with this same problem.
Sadly, google scripts doesn't seem to have a way programmatically set the conditional programming. (Here's a link to the issue. You can cast a vote at the bottom on to get them to implement it)
However, I think you might be able to implement a solution to your problem it by scratch.
The onEdit trigger passes a variable to the function it's assigned to.
function testOnEdit(e) {
Logger.log(JSON.stringify(e));
}
Here is an example of the JSON from the event object for onEdit.
{"range":{"columnStart":3,"rowStart":2,"rowEnd":2,"columnEnd":3},"source":{},"value":"I typed this into B3!","authMode":{},"triggerUid":*********}
You might be able to use this to directly reset the format of the cells they edit. BUT... Unfortunately, onEdit is only fired when a range's values are changed--not when formatting is changed, not when data validation is changed, not when conditional formatting is changed--so to catch the cases that some rouge user decides that purple looks nice, you may want to also make the script run every minute using a timed trigger (but you need to make sure it doesn't take too long to run since it will be going off every minute and google puts a limit on total amount of time triggers can run).
As for comparing dates, these functions may be useful, but be warned that they return the local time (if you have users from multiple time zones you may need to convert to UTC).
function myFunction() {
var date = new Date();
date.getDay(); // the value returned by getDay() is the index of the day of the week, where 0 is Sunday, 1 is Monday...
date.getHours(); // the value returned by getHours() is an integer between 0 and 23
date.getMinutes(); // the value returned by getMinutes() is the current minute in the range 0-60
}
Since I too am dealing with this problem, I plan to add more insight to this solution later. Let me know if you have any insights, Adam.

Related

Automate copying a value from one cell to another on a specific date

I'm looking for more of a pointer to some documentation of a method here rather than an exact solution, I'm happy with JavaScript, haven't done much Apps Script and I'm moderate to OK familiar with Google Sheets functions but far from expert.
I have part of a Google Sheet with some date specific data on it like this:
Date
Some-Value
1 Jan 2023
123
15 Jan 2023
456
... etc
In another part of a sheet I have a cell with the current value of Some-Value. This cell in fact contains a formula that totals a column on another sheet for the values. In case it's not blindingly obvious, these dates are in fact sprint end dates and the Some-Value is a count of story points extracted from JIRA. So sort of like this:
Current Value of Some-Value
345
On exactly the date in the Date column I want to copy the value from the "Current Value of Some Value" cell into the cell in the "Some Value" column adjacent to that date. I don't care if that copy happens only once on that day, or several times on that day, or every hour on that day, or whatever. I just want it to happen at least once, automatically.
Should I be looking at an Apps Script function to do this, and roughly how should I do that? Or is there a simpler way of using some Google Sheets function to copy that cell?
You could potentially do this with formulas if you are willing to enable iterative calculation for your sheet. In that case, you could then write something like =if(A2=today(),currentValueCell,B2) (I'm assuming your Date/Some-value table is in A1:Bx of a sheet and you are placing the above in B2). This will return the current value only if the date matches, and then when the dates no longer match will just maintain whatever value is already present in the cell.
EDIT
Ah yes, I forgot that the initial state of a self-referencing IF is zero (rather than null) until a TRUE occurs. Try =if(A2=today(),currentValueCell,if(B2<>0,B2,)) to hide the initial zero generated when the date in A2 is not equal to TODAY().
If you need to copy a value than then it's going to be changed or erased, yes, you'll need an AppScript. If you already know something, you'll more than able to set a simple function to do it.
You can look into Installable Triggers that will help you to set when and how frequently you want your script to be fired; and it will notify you if there are errors in any of that executions.
Sometimes, matching dates can be tricky, more if you have them previously written. If you have to do a timestamp, then you'll probably succeed at first. Just for have it handy: https://developers.google.com/google-ads/scripts/docs/features/dates
Remember to always use Logger.log() to track the progress of your code and what it's returning. Good luck and here you can always ask for specific struggles you may find!
(I'm no expert at programming by far, since it's copying only a value, you may find useful to record macros and watch them to learn how to copy in the formats you need - only values, paste format, etc. - if you don't copy, you can also research in .getValue() and .setValue() )
In case anyone else comes across this in future, here is the somewhat creaky Apps Script function I developed to do this. It's my first Google Apps Script function so feel free to tear it to shreds.
I then added a trigger to run this function weekly, which was easier than I had thought -- a bit of google searching for Apps Script time based triggers found me the answer.
Nonetheless thanks to the people who answered earlier because the clues you gave me helped me find the answer in the end.
function updateUnderTest() {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = activeSheet.getSheetByName('Supporting Data');
// I12 is the cell that gets updated daily with the story point count
var value = sourceSheet.getRange('I12').getValue();
// D3:E27 is the range containing date : value pairs
var data = sourceSheet.getRange("D3:E27").getValues();
// epochNow will be the current epoch time, in milliseconds
var epochNow = new Date().valueOf();
// Look through the data range to find a matching date
for (nn=0;nn<data.length;++nn) {
// If a match is found to the nearest day, break the loop
if ((data[nn][0].valueOf() < epochNow) && ((epochNow - data[nn][0].valueOf()) < (24*3600*1000) )) {break};
}
// only update if we found a match
if (nn < 24) {
sourceSheet.getRange(nn+3, 5).setValue(value);
}
}

How to schedule a trigger on a script

The situation:
I have sheet containing JSON data that collects personal trading information from a stock exchange. When this API feed retrieves a new line of information (after a new trade has been placed) I need that to act as a trigger to run two Google Scripts to perform their function. These scripts can only be triggered when a new trade has been made, not on a regular time-based trigger.
What have I tried:
Initially, I started off trying onChange/onEdit however, both options will not work because onChange/onEdit search for user-made edits to the cell, which changes to an API feed are not. Because there is no material change to the formula and therefore onChange/onEdit do not react to trigger the script. I have also tried to find a solution for a trigger to activate a script on changes made within a formula (therefore to allow for new data to arriving through an API to trigger the script), but that doesn't appear to be possible.
What am I trying to achieve right now:
I am considering the possibility of establishing a time scheduled trigger via Scripts. Within the API feed, I get confirmation of the date and time a trade has been made. I plan for the script to search the lastRow of certain columns to identify a date and time to trigger this time scheduled script, which in turn will trigger the two other scripts mentioned above.
My coding:
function createTimeDrivenTriggers() {
// Trigger on 2019-12-11 at 21:00.
ScriptApp.newTrigger('priceCalc')
.timeBased()
.atDate(2019, 12, 11)
.atHour(21)
.create();
}
Explanation on coding:
Eventually, the information held within the .atDate() and .atHour() strings will contain information drawn from the lastRow of different columns on Google Sheets to identify the correct integer to feed in to this script. But for now, I am just trying to get this script to work based on fixed values.
In this example above, on 11th December 2019 at 21:00, the createTimeDrivenTriggers script should be triggered, which in turn runs the priceCalc script.
Questions:
1) I cannot get this script to work correctly at the date/time given. What am I doing wrong?
2) I also need to incorporate an .atMinute() and .atSecond() strings here, but that doesn't seem to be available. Can anyone advise how to incorporate this?
3) Finally, if anyone can think of a better way to find a solution for this other than a time scheduled trigger, I am happy to consider other options.
I am very much a novice of scripts, so helpful advise to sort my issue would be greatly appreciated. I have spent days trying to find a solution without any luck.
You can not use atDate(year, month, day) and atHour(hour) together
Indeed, the documentation specifies:
Frequency is required if you are using atHour() or nearMinute()
Instead, you can use at(date) with a date-time string, with the corresponding Javascript syntax.
Sample:
function createTimeDrivenTriggers() {
// Trigger on 2019-12-11 at 21:00.
var time=new Date('2019-12-12T21:00:00');
ScriptApp.newTrigger('priceCalc')
.timeBased()
.at(time)
.create();
}
When you run your code, it generates an error message:
Already chosen a specific date time with at() or atDate()....
What it means is that you cannot use both atDate() and atHour() in the same script.
The solution to creating a "time-of-day-and-hour" trigger is to use at().
The benefit of this is that you can specify a time interval down to seconds and milliseconds.
However, do not be misled. Google undertakes that the script will execute within +/-15 minutes from the specified time. So incorporating "second" parameters might make you feel good, but isn't guaranteed to make any real difference.
Props: #SpencerEaston (https://stackoverflow.com/a/30458103/1330560) for the definition of the date which may well be in the documentation, but I couldn't see it.
For example:
function createTimeDrivenTriggers() {
// Trigger on 2019-12-12 at 21:00.
//var d = new Date(year, month, day, hours, minutes, seconds, milliseconds);
var d = new Date(2019, 12, 12, 21, 00, 00, 00);
ScriptApp.newTrigger('triggertest')
.timeBased()
.at(d)
.create();
}

How to change background color of a cell if it is edited after a specific duration?

I have a google spreadsheet which we use to feed inventory purchase and issue data. The only problem is my staff can manipulate purchased quantity, prices and other variables at a later date. I want that if they enter the data in a cell and try to edit it whenever after 12 hours of entering the data, the cell should get highlighted. If possible, the cell should not highlight if I edit the data.
A simple solution is to have a column when this data is entered (if the data is added with a google form, then you are already set to go on that front). At that point all you need is a onEdit triggered function that fetches that timestamp, does var curTime = new Date() and then check what is the difference between them, and if it's greater than 12 hours you do a e.range.setBackground('red') or whatever color you wish (remember that the function must have e defined like function checker(e)).
Having it ignore edits done by you is also simple, just have an a simple
var editUser = Session.getEffectiveUser().getEmail()
if (editUser == 'myEmail#gmail.com`)
return 1;
and it will stop the script if the session users email matches your own.
For future reference please also provide what code you have so far, as currently we don't know what research you have done so far, what worked and what did not. Remember — here you won't get someone to write the program for you, only solve issues you have with code you already have.

How to write an onOpen triggered script without using Activate() methods

Good morning,
I am having an issue with a piece of script which will open the spreadsheet at the first available row after a data table. I am fairly new to this, but as I understand it from research, the onOpen trigger will not work for scripts featuring Activate() methods (which I assume are things like getActiveRange() and setActiveRange() etc.).
I'd really like for this to work, but I can only do it currently by placing a button at the top of the sheet which the user has to click to run the script. I'm at a loss as to how to write in a different way, and have spent ages googling but only get things which don't help me much at all. My code is as below, any help you can give in writing it without using Activate() methods would be very much appreciated. Thanks, MB
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRange("A2");
var rowCount = range.getValue() + 3;
var newRange = ss.getRange("A" + rowCount);
SpreadsheetApp.setActiveRange(newRange); };
The value on line 4 refers to a value in a cell which is counting the number of rows in the dataset (I had issues trying to count this using script because there are other columns in the dataset which throw functions like getLastRow()).
As mentioned in issue 3928 there is no way (as of now, september 2014) to set a selection on a cell/range/sheet from a triggered function.
This issue was closed as "wont fix" but recently reopened - probably because a lot of people were very disappointed (to use a polite word) - so I suggest you star this issue to increase its visibility and its priority.
In the mean time I'm afraid there is not much we can do except continue to use old version spreadsheet in which this works without problem.
About what you said about counting the last row in a single column, this has been the subject of one of the most popular thread on this forum, look at the suggested solutions there (and also in other similar threads).

When sheet is opened, would like to auto-jump to certain cell based on date

UPDATE: Based on earlier answers, I joined the chorus of people asking Google to re-enable the ability to do this, and they have apparently complied.
Now to figure out how to implement it since I know nothing about scripting in Google Sheets.
ORIGINAL QUESTION BELOW
I have a spreadsheet with a number of different sheets that require daily entry of data. For example, there's a sheet called US-Sales, with a row for each day of the year. There are also several other identical sheets, like UK-Sales, US-Rentals, etc.
I would like focus to auto-jump to the proper row, based on the current date, whenever one of these sheets is opened.
Possible? If so, how?
This could normally be achieved by a simple script with an onOpen() function (that executes on spreadsheet opening) and activates the desired cell (or sheet)...
but, due to a change in the new version of spreadsheets (that you are most probably using) this is not possible anymore (for now), see this issue (3928) and feel free to star it so that hopefully Google team will change their mind and make it possible again ;-)
edit : this simple code works in old version of spreadsheets, it does not in new version.
function onOpen() {
SpreadsheetApp.getActive().getSheets()[0].getRange('B6').activate();// an arbitrary cell
// not worth trying more complex cell selection (on date or anything else) while this is blocked by design .
}
Details on this change here and below.
EDIT 2 :
On october 16 2014 this issue is now fixed and the code above works also in new version of spreadsheets. details of issue here
to automatically activate the row corresponding to the day of the year you can use a code like below :
function onOpen() {
var day = new Date().getDOY();// this uses a custom date method that returns the day of the year and is defined below
SpreadsheetApp.getActive().getSheets()[0].getRange(day,1).activate();// in cloumn 1 for example. Add an offset if necessary (if headers...)
}
Date.prototype.getDOY = function() {
var onejan = new Date(this.getFullYear(),0,1);
return Math.ceil((this - onejan) / 86400000);
}