0

I'm trying to get my sheet to automatically recalculate a set of dates within a schedule, in both directions, when a cell is changed.

The code works fine, but I need to add a bunch more columns and I'd really rather not copy/paste/find/replace a load more times. I'm fairly certain I can do this with variables (just looking up the column identifier and feeding that into the code somehow), but I don't know-how.

functJon onEdJt(e) {

var sh = e.source.getActJveSheet();
Jf(sh.getName() === 'Date Calculator' && e.range.getA1NotatJon() === 'C9' 
) 
{
sh.getRange("C10").setFormula("=WORKDAY(C9,+$C$3)");
sh.getRange("C11").setFormula("=WORKDAY(C10,+10)");
sh.getRange("C12").setFormula("=WORKDAY(C11,+$C$4)");
sh.getRange("C13").setFormula("=WORKDAY(C12,+$C$3)");
sh.getRange("C14").setFormula("=WORKDAY(C13,+10)");
sh.getRange("C15").setFormula("=WORKDAY(C14,+1)");
sh.getRange("C16").setFormula("=WORKDAY(C15,+$C$5)");
}
else Jf (sh.getName() === 'Date Calculator' && e.range.getA1NotatJon() 
=== 'C10' ) 
{
sh.getRange("C9").setFormula("=WORKDAY(C10,-$C$3)");
sh.getRange("C11").setFormula("=WORKDAY(C10,+10)");
sh.getRange("C12").setFormula("=WORKDAY(C11,+$C$4)");
sh.getRange("C13").setFormula("=WORKDAY(C12,+$C$3)");
sh.getRange("C14").setFormula("=WORKDAY(C13,+10)");
sh.getRange("C15").setFormula("=WORKDAY(C14,+1)");
sh.getRange("C16").setFormula("=WORKDAY(C15,+$C$5)");

Ideally the code should then just "work" for any number of columns in the sheet, so I don't need to add more code if I add more columns.

Update Here's an example of what I'm trying (but it's not working) - attempting to check that the active cell is in row 9 of a specific column before then running the "set.Formula" functions:

function onEdit(e) {

  var sh = e.source.getActiveSheet();
  var col = e.source.getActiveSheet().getRange().getColumn();
  var row = e.source.getActiveSheet().getRange().getRow();

  if(sh.getName() === 'Date Calculator' && e.getRange('9',col)     ) 
player0
  • 124,011
  • 12
  • 67
  • 124
  • To clarify, the above code is just a section of the whole thing - there are "Else" statements for all rows up to 16 (I'm sure there's a more efficient way to do that too). – David Jenkins Jul 24 '19 at 09:19
  • Did you find and replace "i" with "J" by any chance? – ross Jul 24 '19 at 09:20
  • Oh yes haha well spotted - evidence of my attempt to do a find & replace and how poorly that was going! Fortunately that was on a "work in progress" version - the backup still has the original code without the "ActJve". – David Jenkins Jul 24 '19 at 09:23
  • Use the [Event Objects](https://developers.google.com/apps-script/guides/triggers/events) from onEdit(e). From the range, you can `getRow` and `getColumn` and then use those values to calculate the target ranges. Regarding using variables in `setformula`, look at the answer in [Multiplying numbers by certain number](https://stackoverflow.com/a/57015805/1330560) and more generally [SpreadsheetApp how to use variable inside formula](https://stackoverflow.com/q/47704060/1330560). – Tedinoz Jul 24 '19 at 11:35
  • @Tedinoz so: `var sh = e.source.getActiveSheet(); var col = e.source.getColumn();` Like that? – David Jenkins Jul 24 '19 at 12:25
  • If I'm trying to say that I want to run the first set of calculations if the column is _active column_9 (e.g. C9) would this work? I feel like there may be some issue with the column identifier being 0 based (so column C is actually column 2), but not sure if that's true or how to account for it :( I've added a snip of the code under **Update** in the initial question. – David Jenkins Jul 24 '19 at 13:25

1 Answers1

1

Event Objects
Even though the code was written as onEdit(e), you didn't take advantage of the Event Objects. In this answer, the code returns the new value of the edited cell and also the range. The range is then used to work out the row, column and sheet name and these is used for validation as well as for building the ranges and the setFormula

Variables
The code includes variables for the valid range of columns that can be used for data entry (Column C to Column H), and respective input rows (rows 9 and 10). These are expressed as values, but they could just as easily be written into the spreadsheet as assumptions and the values obtained in the code by using getValue.
The absolute cell references used in the setFormula are partly variable (column reference) and part hard-coded (the respective rows-3,4 and 5). If desired, the rows could be variable as well.

Efficiency
There is just one if statement containing one version of the code to build setFormula. This is achieved by designing the if statement:
1. if the sheet = "Date Calculator" AND
2. if the editColumn is between the valid ColumnStart and ColumnEnd values (Column C to H) AND
3. if the editRow is between the valid Row values (rows 9 or 10) AND
4. if the edited value isn't a blank (length != 0).

The last condition ("edited value is blank") ensures that if cell contents are been deleted (and/or have no value), then the code won't proceed.

Convert column number to letter
I used a routine written by @AdamL found at Convert column index into corresponding column letter; this converts a column number into a letter. It's used to build the "targetcolumn" address in Workdays. It's valid for the letters A-Z; there's a version for letters beyond Z.

Cleanup
If data is entered into row 10 of a given column, then any value in row 9 (of the same column) needs to be deleted. The code does this and also deletes any pre-existing formula dates in the rows below so there is no confusion about the dates derived by the data entry.


function onEdit(e){

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheetname = "Date Calculator";
  var sheet = ss.getSheetByName(sheetname);

  // get the event source data
  var editedCell = e.range;
  var editRow = editedCell.getRow();
  var editCol = editedCell.getColumn();
  var eValue = e.value;
  var editedSheet = editedCell.getSheet().getName();
  //Logger.log("DEBUG: the cell = "+editedCell.getA1Notation()+", the column = "+editCol+", the row is "+editRow+", the value is "+eValue+", the edited sheet is "+editedSheet);

  // create some variables for column and row range
  var columnStart = 3; // Column C
  var columnEnd = 8; // Column H
  var rowOption1 = 9; // row 9
  var rowOption2 = 10 // row 10

  // create some variables for target cells
  var absolutecolumn = "C";
  //var absoluterow1 = 3; // not used
  //var absoluterow2 = 4; // not used
  //var absoluterow3 = 5; // not used

  // test for valid edit in row option 1 // Row 9
  if(editedSheet === sheetname && columnEnd >=editCol && editCol>=columnStart && rowOption2>=editRow && editRow>=rowOption1 && eValue.length !=0 ){

    //Logger.log("DEBUG: You got the right sheet, the edit is in the right range of columns and the edited row was =  "+rowOption1);

    if (editRow == rowOption2){
    // clear row 9
    sheet.getRange((+editRow-1),editCol).clear();
    } 

    // clear following 8 rows of data
    sheet.getRange((+editRow+1),editCol,8).clear();

    // set the targetcolumn as a letter
    var targetcolumn = columnToLetter(editCol);

    // set formula for row+1
    sheet.getRange((+editRow+1),editCol).setFormula("=WORKDAY("+targetcolumn+editRow+",$"+absolutecolumn+"$3)"); // 
    // set formula row +2
    sheet.getRange((+editRow+2),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+1)+",+10)");
    // set formula row +3
    sheet.getRange((+editRow+3),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+2)+",$"+absolutecolumn+"$4)");
    // set formula row +4
    sheet.getRange((+editRow+4),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+3)+",$"+absolutecolumn+"$3)");
    // set formula row + 5
    sheet.getRange((+editRow+5),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+4)+",+10)");
    // set formula row + 6
    sheet.getRange((+editRow+6),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+5)+",+1)");
    // set formula row + 7
    sheet.getRange((+editRow+7),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+6)+",$"+absolutecolumn+"$5)");

    // change the background to show entry in rowoption1
    sheet.getRange(editRow,editCol).setBackground("yellow");
    sheet.getRange((+editRow+1),editCol).setBackground("white");
  } 
}
function columnToLetter(column)
{
  var temp, letter = '';
  while (column > 0)
  {
    temp = (column - 1) % 26;
    letter = String.fromCharCode(temp + 65) + letter;
    column = (column - temp - 1) / 26;
  }
  return letter;
}

Screenshot
Screenshot

Tedinoz
  • 5,911
  • 3
  • 25
  • 35
  • That works perfectly in terms of easily adding more columns (thank you so much!). Could you explain more about the Row variables that aren't used and how they could be used to do the rest of the formula changes so that, for instance, if the edit is made in row 10, rather than row 9, rows 11-16 (or beyond) have their respective formula entered, and row 9 has the formula entered which does the inverse (i.e. takes away days, rather than adding them)? – David Jenkins Jul 26 '19 at 08:46
  • Or do I just need some "else" statements"? :) – David Jenkins Jul 26 '19 at 09:41
  • Many formula use absolute references. E.g. Row#1:`=WORKDAY(C9,+$C$3)` and elements of these are built with variables. For example, "$C"='$"+absolutecolumn'. If it was justified, then "$3" could be represented by '$"+absoluterow1'. Whether this would be worthwhile, I couldn't say. Regarding "row 9 has the formula entered which does the inverse (i.e. takes away days, rather than adding them)" - you don't need more if/else statements. There is already an IF statement to detect an edit on Row 10; presently this deletes the contents of row#9, but it could just as easily insert an formula in row #9. – Tedinoz Jul 26 '19 at 10:38
  • I've just noticed something in your code that I overlooked. For input on row#9, row#10 is "+$C$3"; but for input on row#10, row#11 is "-$C$3". All the other row formulae are duplicates. Is this a typo in your code, or is the first row reference actually different between row#9 and row#10? – Tedinoz Jul 26 '19 at 10:44
  • Nevermind I did it :) I also managed to add my own variable so that certain things are calculated differently depending on the column. Thank you so much! – David Jenkins Jul 26 '19 at 11:00