3
votes

I want to invoke custom methods on Google Apps Script classes such as Spreadsheet, Sheet, and DriveApp. In https://stackoverflow.com/a/6117889, a minimal prototyping solution is used to add a method to the Javascript Date() class to get the week number. Is it possible to apply the same strategy to Google Apps Script classes?

As an example, I want to create a custom method for the Spreadsheet class that allows the spreadsheet to be moved to a particular folder in my google drive given the ID of that folder. Here is what I've tried:

Spreadsheet.prototype.moveToFolder = function(folderID) {
  const file = DriveApp.getFileById(this.getId());
  const destination = DriveApp.getFolderById(folderID);
  file.moveTo(destination);
}

However, I get the error message "ReferenceError: Spreadsheet is not defined". Is there another way to achieve what I want?

2

2 Answers

2
votes

It is possible to add custom methods. But Spreadsheet class is not directly accessible. So, it is needed to first get a instance of Spreadsheet class using any of methods available:

const Spreadsheet = SpreadsheetApp.getActive();

Then use Object.getPrototypeOf() on the Spreadsheet instance to get it's prototype.

Object.getPrototypeOf(Spreadsheet).myMethod = function (){
  console.info("myMethod was called!")
  return true;
}

Any property defined on the prototype will then propogate through all Spreadsheet instances.

Update:

The prototype object returned by Object.getPrototypeOf(Spreadsheet) is Object. This can also be confirmed by logging Spreadsheet.constructor.name. This means that there is no special Spreadsheet prototype or constructor used to create the Spreadsheet instance. Therefore, Although you can add custom methods, They're added to all objects say, Range, DriveApp and any object created with var obj = {} or Object.create("Any object except null").

1
votes

Given that Spreadsheet doesn't have a unique prototype but actually uses that of Object, as noted by TheMaster, you can simply add your method to the Object prototype.

Object.prototype.moveToFolder = function(folderID) {
  const file = DriveApp.getFileById(this.getId());
  const destination = DriveApp.getFolderById(folderID);
  file.moveTo(destination);
}

Since this method will apply to all objects, you should ask yourself if it's really worth doing. See "Why is extending native objects a bad practice?"

Instead of modifying the native object, you could create a new class that "inherits" the native methods while also giving you the ability to override and add new methods.

function main() {
  const ss = new Spreadsheet(SpreadsheetApp.getActive());
  console.log(ss._native.getName()); // MySpreadsheet
  console.log(ss.getName()); // The name is MySpreadsheet
  
  ss.moveToFolder(FOLDER_ID);
}

class Spreadsheet {
  constructor(native) {
    this._native = native;
    
    // Copy native's methods to this
    Object.getOwnPropertyNames(this._native).forEach(property => {
      this[property] = this._native[property];
    });
    
    // Override native methods
    Object.defineProperties(this, {
      'getName': {
        value: function() {
          return `The name is ${this._native.getName()}`;
        }
      }
    });
  }
  
  moveToFolder(folderId) {
    const file = DriveApp.getFileById(this.getId());
    const destination = DriveApp.getFolderById(folderId);
    file.moveTo(destination);
  }
}