While @Rubén's answer is correct (and will remain accepted), for future reference I did actually come up with a script that completely sorts out my problem, though it's a little quirky.
function onEdit(e) {
var sleepTime = 5000;
var lastEditedTime = new Date().getTime();
PropertiesService.getScriptProperties().setProperty('lastEditedTime', lastEditedTime);
Utilities.sleep(sleepTime);
var newTime = new Date().getTime();
if(newTime - PropertiesService.getScriptProperties().getProperty('lastEditedTime') < sleepTime - 50) {
return;
}
var range = e.range; //range just edited
var sheet = range.getSheet();
if(sheet.getName() !== 'Tasks') {
return;
}
var rules = sheet.getConditionalFormatRules();
var newRules = [];
for(var r = 0; r < rules.length; r++) {
var booleanCondition = rules[r].getBooleanCondition();
if(booleanCondition != null) {
var rule = SpreadsheetApp.newConditionalFormatRule()
.withCriteria(booleanCondition.getCriteriaType(), booleanCondition.getCriteriaValues())
.setBackground(booleanCondition.getBackground())
.setRanges([sheet.getRange("E:E"),sheet.getRange("G:G")])
.build();
newRules.push(rule);
sheet.setConditionalFormatRules(newRules);
}
}
};
Since onEdit
is called for every edit you make, and the conditional formatting takes about 0.5 - 1 second to update, if you make several edits very quickly the script will run several times very close together, and subsequent runs after the first one may try to read the conditional formatting rules while they're still updating, and end up effectively removing some or all rules.
This would be easy to fix if we could use setTimeout
, but Google Apps Scripts are synchronous and don't support that function. Instead each time the script runs it records the current timestamp in a property (which is global across script executions), then sleeps for 5 seconds. If the current time after waking is less than ~4.95 seconds after the value recorded in the lastEditedTime
property, that means the script has been run at least once more since this execution (because the lastEditedTime
value has been updated to a later time). Therefore this isn't the most recent execution of the script so we just return and do nothing.
Then it's a simple matter of reading the current conditional formatting rules and writing them back again, but with the correct ranges set.
The script works pretty well and so far hasn't screwed anything up, but sometimes I notice it run twice. I'm not sure if I'm somehow making two edits less than 50ms apart or if it's something to do with the way the scripts get executed on Google's servers but it hasn't caused problems so far.