1

I have a script that has been working wonderfully the past few weeks, until this morning when it started failing due to there being too many installable triggers on the project.

The script on the main spreadsheet generates new spreadsheets from a template. These new spreadsheets need an installable onEdit trigger to edit values back on the original (main) spreadsheet. I have a function that worked before for generating a new installable trigger, however this was added to the main spreadsheet, not the new one. The issue obviously is that there's a hard limit to the amount of installable triggers that can be tied to one spreadsheet, so I'd like a way to get them associated with the newly generated spreadsheets instead.

I've asked this question before without much resolution, first in the comments here and then in a question here. I also found a very similar question asked four years ago (the answer to this one said there was no solution, I just want to see if anything's changed in four years).

When I tried using my new spreadsheet's ID in my createInstallableTrigger function, it still just kept generating triggers in the main spreadsheet.

I need to know if what I'm asking for is even possible, but if it's not, I'll need a new solution to this problem. The code I'm showing below DOES work, but only up until the limit on triggers is reached.

These are the relevant functions:

 function createInstallableTrigger(funcName,ssId) {
      if(!isTrigger()) {
        ScriptApp.newTrigger(funcName).forSpreadsheet(ssId).onEdit().create();
      }
    }

    function isTrigger(funcName){
      var r=false;
      if(funcName){
        var allTriggers=ScriptApp.getProjectTriggers();
        for(var i=0;i<allTriggers.length;i++){
          if(funcName==allTriggers[i].getHandlerFunction()){
            r=true;
            break;
          }
        }
      }
      return r;
    }       

Here's the whole script:

   function main(e) {
      var ss = SpreadsheetApp.getActiveSpreadsheet();
      var sheet = ss.getActiveSheet();
      var range = e.range;
      var newId;
      var newName;
      var hyperlinkString;
      var isJob;
      var i;
      if(sheet.getRange('L1').getValue() == 10) return;
      //Check if edit occurred in relevant range
      if((range.getColumn() !== 1) && (range.getColumn() !== 2)) return;
      if((range.getRow() == 1) || (range.getRow() == 2)) return;

      //Check if both columns were filled after edit
      if((range.getColumn() == 1) && (range.offset(0,1).isBlank() == true)) return;
      if((range.getColumn() == 2) && (range.offset(0,-1).isBlank() == true)) return;

      //Get new document name from concatenate formula in column H
      if(range.getColumn() == 1) newName = range.offset(0,7).getValue();
      if(range.getColumn() == 2) newName = range.offset(0,6).getValue();

      //Check whether the edits occurred on the jobs list or deliveries list (indicated by '4' or '5' in L1)
      //Calls function to create new job sheet or delivery sheet from template, gets ID of new spreadsheet
      if(((range.getColumn() == 1) && (range.offset(0,3).isBlank() == true)) || ((range.getColumn() == 2) && (range.offset(0,2).isBlank() == true))) { 
        if(sheet.getRange('L1').getValue() == 4) {
          if(range.getColumn() == 1) range.offset(0,3).setValue("Please wait");
          if(range.getColumn() == 2) range.offset(0,2).setValue("Please wait");
          newId = newJob();
          isJob = 1;
        }
        if(sheet.getRange('L1').getValue() == 5) {
          if(range.getColumn() == 1) range.offset(0,3).setValue("Please wait");
          if(range.getColumn() == 2) range.offset(0,2).setValue("Please wait");
          newId = newdelivery();
          isJob = 0;
        }
      }
      //Updates hyperlink if sheet already exists
      else {
        if(range.getColumn() == 1) {
          hyperlinkString = range.offset(0,3).getFormula();
          newId = hyperlinkString.substring(77,121);
        }
        if(range.getColumn() == 2) {
          hyperlinkString = range.offset(0,2).getFormula();
          newId = hyperlinkString.substring(77,121);
        }
        return;
      }

      //Set the name of the new spreadsheet
      SpreadsheetApp.openById(newId).rename(newName);

      //Enter name and date information onto new spreadsheet
      if((sheet.getRange('L1').getValue() == 4) || (sheet.getRange('L1').getValue() == 0) ||  (sheet.getRange('L1').getValue() == 1)) isJob = 1;
      else isJob = 0;
      //If it's on a job, update all sheets on itemization spreadsheet (rooftop/electrical pick/return)
      if(isJob == 1) {
        //If name is entered, update name first then date
        if(range.getColumn() == 1) {
          for(i=0;i<4;i++) {
            SpreadsheetApp.openById(newId).getSheets()[i].getRange('B1').setValue(range.getValue());
            SpreadsheetApp.openById(newId).getSheets()[i].getRange('B2').setValue(range.offset(0,1).getValue());
            }
        }
        //If date is entered, update date first then name
        if(range.getColumn() == 2) {
          for(i=0;i<4;i++) {
            SpreadsheetApp.openById(newId).getSheets()[i].getRange('B2').setValue(range.getValue());
            SpreadsheetApp.openById(newId).getSheets()[i].getRange('B1').setValue(range.offset(0,-1).getValue());
            }
        }

      }
      //If it's on an incoming delivery, just update the main (only) sheet
      if(isJob == 0) {
        //If name is entered, update name first then date
        if(range.getColumn() == 1) {
            SpreadsheetApp.openById(newId).getSheets()[0].getRange('B1').setValue(range.getValue());
            SpreadsheetApp.openById(newId).getSheets()[0].getRange('B2').setValue(range.offset(0,1).getValue());   
        }
        //If date is entered, update date first then name
        if(range.getColumn() == 2) {
            SpreadsheetApp.openById(newId).getSheets()[0].getRange('B2').setValue(range.getValue());
            SpreadsheetApp.openById(newId).getSheets()[0].getRange('B1').setValue(range.offset(0,-1).getValue());
        }
      }


      //Creates hyperlink to new spreadsheet
      if (range.getColumn() == 1) range.offset(0,3).setFormula("=HYPERLINK(\"" + SpreadsheetApp.openById(newId).getUrl() +"\",\"Click here for itemization\")");
      if (range.getColumn() == 2) range.offset(0,2).setFormula("=HYPERLINK(\"" + SpreadsheetApp.openById(newId).getUrl() +"\",\"Click here for itemization\")");

      //Sort list descending from most recent date
      sheet.getRange("A3:D1000").sort({column: 2, ascending: false});
    }

    function newJob() {
      //Open template
      var jobTemplateSS = SpreadsheetApp.openById("ID Redacted");
      //Create new spreadsheet from copy of template spreadsheet
      var newSS = jobTemplateSS.copy("Untitled Job");
      //Get folder
      var jobFolder = DriveApp.getFolderById("ID Redacted");
      //Get ID of new file
      var newSSFile = DriveApp.getFileById(newSS.getId());
      //Copy file to the correct directory and delete the instance created in root
      jobFolder.addFile(newSSFile);
      DriveApp.getRootFolder().removeFile(newSSFile);
      //Automatically generate an installable trigger on new SS that detects edits
      createInstallableTrigger('IncrementDecrement',newSS.getId());
      //Pass ID of new spreadsheet back to calling function
      return(newSS.getId());
    } 

    function newdelivery() {
      //Open template
      var deliveryTemplateSS = SpreadsheetApp.openById("ID Redacted");

      //Create new spreadsheet from copy of template spreadsheet
      var newSS = deliveryTemplateSS.copy("Untitled Delivery");

      //Get folder
      var deliveryFolder = DriveApp.getFolderById("ID Redacted");

      //Get ID of new file
      var newSSFile = DriveApp.getFileById(newSS.getId());

      //Copy file to the correct directory and delete the instance created in root
      deliveryFolder.addFile(newSSFile);
      DriveApp.getRootFolder().removeFile(newSSFile);
      createInstallableTrigger('IncrementDecrement',newSS.getId());

      //Pass ID of new spreadsheet back to calling function
      return(newSS.getId());
    }

    function createInstallableTrigger(funcName,ssId) {
      if(!isTrigger()) {
        ScriptApp.newTrigger(funcName).forSpreadsheet(ssId).onEdit().create();
      }
    }

    function isTrigger(funcName){
      var r=false;
      if(funcName){
        var allTriggers=ScriptApp.getProjectTriggers();
        for(var i=0;i<allTriggers.length;i++){
          if(funcName==allTriggers[i].getHandlerFunction()){
            r=true;
            break;
          }
        }
      }
      return r;
    }

    function  IncrementDecrement(e) {
      var ss = SpreadsheetApp.getActiveSpreadsheet();
      var sheet = ss.getActiveSheet();
      var range = e.range;

      //Check if edit occurred in valid range, and whether the edit occurred on a job or delivery sheet (indicated by a 0,1,or 2 in L1)
      if ((range.getColumn() !== 1) && (range.getColumn() !== 3) && (range.getColumn() !== 4) && (range.getColumn() !== 5) && (range.getColumn() !== 6)) return;
      if(range.getRow() < 4) return;
      if ((sheet.getRange('L1').getValue() !== 0) && (sheet.getRange('L1').getValue() !== 1) && (sheet.getRange('L1').getValue() !==2)) return;

      //Wait to declare all variables until checks occur to maximize efficiency
      var masterSS = SpreadsheetApp.openById("ID Redacted");
      var invsheet = masterSS.getSheetByName("Inventory");
      var q;
      var deltaQ;
      var sku;
      var nextCell;
      var currentCell;
      var cellLink;
      var total;
      var desc;
      var i;

      //Set current cell to work on as the edited cell
      currentCell = range;

      //If edit occurred in first column (SKU) fetch description & value from inventory
      if(range.getColumn() == 1) {
        //Save SKU
        sku = currentCell.getValue();

        //Save current cell
        cellLink = currentCell;

        //Scan through inventory sheet and find matching SKU line, fetch info
        currentCell = invsheet.getRange('A3');
        while (currentCell.isBlank()==false) {
          if(currentCell.getValue() == sku) {
            desc = currentCell.offset(0,1).getValue();
            q = currentCell.offset(0,2).getValue();
            currentCell = cellLink;
            break;
          }
          currentCell = currentCell.offset(1,0);
        }
        //Return to saved cell
        currentCell = cellLink;

        //Enter description and value on itemization sheet
        currentCell.offset(0,1).setValue(desc);
        currentCell.offset(0,3).setValue(q);
      }

      //If edit occurred in third column (quantity)
      if(range.getColumn() == 3) {
        //Save quantity and SKU
        q = currentCell.getValue();
        sku = currentCell.offset(0,-2).getValue();

        //Saving prior quantity value. If no value, set to 0.
        currentCell = currentCell.offset(0,8);
        if (currentCell.isBlank() == true) currentCell.setValue(0);

        //Find and save the change in quantity by subtracting new quantity by prior quantity
        currentCell = currentCell.offset(0,1);
        currentCell.setValue(q - currentCell.offset(0,-1).getValue());
        deltaQ = currentCell.getValue();

        //Save current cell
        cellLink = currentCell;

        //Search through inventory sheet and find matching SKU line
        currentCell = invsheet.getRange('A3');
        while (currentCell.isBlank()==false) {
          if(currentCell.getValue() == sku) {
            //If it's a pick ticket, subtract the quantity used from inventory quantity
            if (sheet.getRange('L1').getValue() == 0) {
              currentCell.offset(0,3).setValue(currentCell.offset(0,3).getValue() - deltaQ);
              currentCell.offset(0,4).setValue(currentCell.offset(0,4).getValue() - deltaQ);

              //Update total price on job list
              for(i=3; i<1000; i++) {
                if(masterSS.getSheetByName("Job List").getRange(i,1).getValue() == sheet.getRange(1,2).getValue()) {
                  masterSS.getSheetByName("Job List").getRange(i,1).offset(0,2).setValue(sheet.getRange(1,15).getValue());
                  break;
                }
              }
            }
            //If it's a return ticket or inbound delivery, add the quantity returned/received to the inventory quantity 
            if ((sheet.getRange('L1').getValue() == 1) || (sheet.getRange('L1').getValue() == 2)) {
              Logger.log(q);
              Logger.log(currentCell.offset(0,3).getValue());
              Logger.log(deltaQ);
              Logger.log(currentCell.offset(0,3).getValue() + deltaQ);
              currentCell.offset(0,3).setValue(+(currentCell.offset(0,3).getValue()) + +deltaQ);
              currentCell.offset(0,4).setValue(+(currentCell.offset(0,4).getValue()) + +deltaQ);

              //If it's a return ticket, update total price on job list
              if(sheet.getRange('L1').getValue() == 1) {
                for(i=3; i<1000; i++) {
                  if(masterSS.getSheetByName("Job List").getRange(i,1).getValue() == sheet.getRange(1,2).getValue()) {
                    masterSS.getSheetByName("Job List").getRange(i,1).offset(0,2).setValue(sheet.getRange(1,15).getValue());
                    break;
                  }
                }
              }
              //If it's an inbound delivery, update total price on delivery list.
              if(sheet.getRange('L1').getValue() == 2){ 
                for(i=3; i<1000; i++) {
                  if(masterSS.getSheetByName("Delivery List").getRange(i,1).getValue() == sheet.getRange(1,2).getValue()) {
                    masterSS.getSheetByName("Delivery List").getRange(i,1).offset(0,2).setValue(sheet.getRange(4,7).getValue());
                    break;
                  }
                }
              }
            }
          }
          //Increment for the 'while loop' that searches for SKU in inventory
          nextCell = currentCell.offset(1,0);
          currentCell = nextCell;
        }

        //Return to saved cell
        currentCell = cellLink;
        //Change value of 'prior quantity' to new quantity in case of further changes
        currentCell = currentCell.offset(0,-1);
        currentCell.setValue(q);

        //Return to orginally edited cell
        currentCell = currentCell.offset(0,-8);
      }

      //If edit occurred in fourth, fifth, sixth columns (value, ext. value, total cost)
      if((range.getColumn() == 4) || (range.getColumn() == 5) || (range.getColumn() == 6)) {
        //If on job sheet, update total price on job list
        if ((sheet.getRange('L1').getValue() == 0) || (sheet.getRange('L1').getValue()== 1)) {
            for(i=3; i<1000; i++) {
              if(masterSS.getSheetByName("Job List").getRange(i,1).getValue() == sheet.getRange(1,2).getValue()) {
                masterSS.getSheetByName("Job List").getRange(i,1).offset(0,2).setValue(sheet.getRange(4,6).getValue());
                break;
              }
            }
          }
        //If on delivery sheet, udate total price on delivery list
        if (sheet.getRange('L1').getValue() == 2) {
          for(i=3; i<1000; i++) {
            if(masterSS.getSheetByName("Delivery List").getRange(i,1).getValue() == sheet.getRange(1,2).getValue()) {
              masterSS.getSheetByName("Delivery List").getRange(i,1).offset(0,2).setValue(sheet.getRange(4,7).getValue())
              break;
            }
          }
        }
      }
    }

Thanks for the advice y'all. I'm just a summer intern trying not to have my work fall apart as soon as I leave here :)

Alex S.
  • 61
  • 7
  • What kind of account do you own? You can check [here](https://developers.google.com/apps-script/guides/services/quotas) the current quotas for the different accounts, so you might be interested in upgrading it. – Jescanellas Jul 02 '19 at 12:58
  • All of the options have a 20/user/script trigger limit. – Alex S. Jul 02 '19 at 14:59
  • That's the limit Google wants then, it can't be bypassed. – Jescanellas Jul 02 '19 at 15:07
  • I understand. I'd like to find a way to have the triggers be generated on the new spreadsheet (and I'd put the function they trigger onto the template if that were the case). – Alex S. Jul 02 '19 at 15:17
  • Sadly, only user actions can activate triggers, it [can't be done](https://developers.google.com/apps-script/guides/triggers/installable#restrictions) from the script execution. – Jescanellas Jul 02 '19 at 15:28
  • That's not really what I had in mind. All I'm wanting is for my script on the main spreadsheet to create an installable trigger on another spreadsheet. I don't need the new trigger to be activated by the script, I just need it to be created. – Alex S. Jul 02 '19 at 15:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/195868/discussion-between-alex-s-and-jescanellas). – Alex S. Jul 02 '19 at 15:50

0 Answers0