Extend, Inherit
I did some work in Google Apps Script for a friend recently. After a while, I had a number of disorganized helper functions that were basically extensions for various objects from GAS Default Services (mostly batching more methods together, etc.). Later, I started wondering if there is a way to extend GAS objects directly. Unfortunately, there is currently no way to do that. And according to this issue with won't fix resolution there will never be one.
So I decided to put the related functions to separated classes. And I wanted some sort of inheritance so that I could have something like this:
// Create GAS object var table = uiApp.createFlexTable(); // Some basic table writer for generic usage var writer = new TableWriter(table); writer.writeRow(["a", "b", "c"], style); // Specialized table generator, subclass of generic TableWriter var builder = new ReportBuilder(table); builder.addOrder(order);
Being a JavaScript neophyte I turned to googling—and a search for a simple JavaScript inheritance yielded just that, Simple Javascript Inheritance by John Resig. Someone tried to use it in GAS before with no success but maybe things have changed since then?
It didn't work when I tried it at first, complaining that root class called Class is undefined (this was all in the same GAS library project so problems with export from library described in the issue linked in previous paragraph would not apply). Then it struck me—the problem could lie in the execution order of individual GS script files in the project (I had subclasses in the different file that the base class). After some googling, it became apparent that the execution order is undefined. The base class was defined in self-executing anonymous function but GS file with definitions of subclasses was actually executed earlier. My solution is not elegant but works: change self-executing function to a regular one and call it explicitly at the beginning of GS file with subclasses. Fortunately, it is not a problem in main GAS project—all GS files of the libraries are executed before GS files of the main project.
Also, note to myself for possible future reference:
var SomeSubclass = Class.extend({ classProp: { }, init: function(param) { this.classProp[param] = 1; this.instProp = { }; this.instProp[param] = 1; } }); var obj1 = new SomeSubclass("a"); var obj2 = new SomeSubclass("b"); // And now: obj1 == {instProp: {a:1}, classProp: {b:1, a:1}} obj2 == {instProp: {b:1}, classProp: {b:1, a:1}}
Handling Tab Selection
Another thing I needed was to get currently selected tab in TabPanel widget (UI default service). There is no getSelectedTab method; you have to register a selection handler (I needed a server handler). You can google some info about this but mostly referring to handler created by createServerSelectionHandler method of UiInstance. However, this method and many other specialized server handlers have been deprecated and only one ServerHandler for all uses exists now. How does it report selected tab index though? I could not find any info on this so I logged the event parameter to see what it gets. And the result is:
var tabs = app.createTabPanel(); tabs.addSelectionHandler(app.createServerHandler("tabSelectedEvent")); ... function tabSelectedEvent(e) { Logger.log("tab sel: " + Utilities.jsonStringify(e.parameter)); // Logs something like this: // {"source":"u29743196428","eventType":"selection","u29743196428":"5"} var sourceId = e.parameter.source; var tabIdx = e.parameter[sourceId]; // use tabIdx and profit... } function tabSelectedEvent2(e) { // If you set ID of TabPanel to "tabPanel": // {"source":"tabPanel","eventType":"selection","tabPanel":"5"} var tabIdx = e.parameter.tabPanel; }