OiO.lk Community platform!

Oio.lk is an excellent forum for developers, providing a wide range of resources, discussions, and support for those in the developer community. Join oio.lk today to connect with like-minded professionals, share insights, and stay updated on the latest trends and technologies in the development field.
  You need to log in or register to access the solved answers to this problem.
  • You have reached the maximum number of guest views allowed
  • Please register below to remove this limitation

Unexpected behavior after writing class-based Apps Script code

  • Thread starter Thread starter NeophyteLou
  • Start date Start date
N

NeophyteLou

Guest
I've been having a problem with class logic pattern in my google apps script project, and I'm not sure if it's my logica, .gs file arrangement, or if this is just unexpected behavior with classes in the google V8 engine. Nonetheless, I cannot figure out why this code won't run. It's a google apps script project attached to a spreadsheet:

(https://docs.google.com/spreadsheets/d/120mXQ5v4XtBeUQGAv57WkXuFhZBGwn6mywEY5vGU_Nw/edit?usp=sharing)

For context, I am trying to understand dependency injection and coupling. I created a class, "TocSheet.gs" that manages a table of contents, and of course relies on Spreadsheet App for functionality as well as PropertiesService to store the state of a TocSheet instance. Any insight would be greatly appreciated, the link is available to the public. To access the code, click Extensions > Apps Script. Thanks for any help you can offer.

Localized issue: In the TocSheet.gs file, you will see that I'm attempting to inject two "dependencies" into the class TocSheet constructor as parameters (spreadsheet App: new SpreadsheetUtil() and propsService: new PropertiesServiceStorage()). The execution log for some reason shows that the SpreadsheetUtil object was loaded into the spreadsheetApp variable and I'm not sure why.

UiManager.gs

Code:
class UiUtil {
    constructor(spreadsheet) {
        this.uI = spreadsheet().getUi();
    }

    alert(message) {
        return this.uI.alert(message);
    }

    createMenu() {
        return this.uI
            .createAddonMenu()
            .addItem("Insert", "start")
            .addSeparator()
            .addItem("Remove", "confirmDelete")
            .addToUi();
    }

    confirmDelete() {
        const response = this.uI.alert(
            "Confirm Deletion",
            "Delete this item?",
            this.ui.ButtonSet.OK_CANCEL
        );

        if (response) {
            return true;
        } else {
            this.uI.alert("Deletion Cancelled");
        }
        return false;
    }
}

function start() {
    const uI = getSpreadsheetApp().getUi();
    const sheet = undefined; //JSON.parse(getPropsService().getScriptProperties().getProperty("sheet"))
    //console.log("sheet from props service: ", sheet)
    if (!sheet) {
        const spreadsheetApp = new SpreadsheetUtility();
        const propsService = new PropertiesServiceStorage();
        const myToc = new TocSheet(spreadsheetApp, propsService);
        uI.alert("sheet created");
        console.log(myToc);
    }
}

// function setFalse(){
//   const doSort = false
//   const tocSheet = new SheetManager("TOC", getSpreadsheetApp, getPropsService,getScriptApp);
// }

function confirmDelete() {
    const isConfirmed = Ui.confirmDelete;
    if (isConfirmed) {
        removeToc(isConfirmed);
    }
}

SpreadsheetUtility.gs

Code:
class SpreadsheetUtility {
    constructor() {
        this.activeSheet = SpreadsheetApp.getActive();
    }
    insertSheet(name) {
        return this.activeSheet.insertSheet(name, 0);
    }

    setNamedRange(name, range) {
        this.activeSheet.setNamedRange(name, range);
    }

    getSheets() {
        return this.activeSheet.getSheets();
    }

    getSheetByName(name) {
        return this.activeSheet.getSheetByName(name);
    }
}

function myFunction() {
    console.log(new SpreadsheetUtility().getSheets());
}

PropertiesService.gs

Code:
class PropertiesServiceStorage {
    constructor() {
        this.key = "tocSheet";
    }

    save(tocData) {
        const data = JSON.stringify(tocData);
        PropertiesService.getScriptProperties().setProperty(this.key, data);
    }

    load() {
        const data = PropertiesService.getScriptProperties().getProperty(
            this.key
        );
        if (!data) {
            return null;
        }
        return JSON.parse(data);
    }
}

TocSheet.gs

Code:
class TocSheet {
    constructor(name, spreadsheetApp, propsService) {
        console.log("From Toc: Spreadsheet: ", spreadsheetApp, propsService);

        this.name = name || "Table of Contents";
        this.spreadsheetApp = spreadsheetApp || new SpreadsheetUtility();
        this.propsService = propsService;
        console.log("spreadsheet property: ", this.spreadsheetApp);
        this.propsService = propsService || new PropertiesServiceStorage();
        this.properties = {
            name: this.name,
            sheetId: null,
            titles: null,
            range: {
                headerName: "TOCHeader",
                tocName: "TOC",
            },
        };
        this.initialize();
    }

    static load() {
        const storage = new PropertiesServiceStorage();
        storage.load();
    }

    setNamedRanges() {
        const numRows = this.titles.length;
        this.sheet.setNamedRange(this.range.headerName, sheet.getRange(1, 1));
        this.sheet.setNamedRange(
            this.range.tocName,
            sheet.getRange(2, 1, numRows)
        );
    }

    initialize() {
        this.createSheet();
        this.setSheetId();
        this.setTitles();
        this.setNamedRanges();
        //setTitleLinks();
    }

    createSheet() {
        console.log("name", this.name);
        const sheet = this.spreadsheetApp.insertSheet("Table of Contents");
        this.sheet = sheet;
    }

    setSheetId() {
        this.properties.sheetId = this.sheet.getSheetId();
    }

    setTitles(titles = null) {
        if (titles) {
            this.properties.titles = titles;
        } else {
            const sheets = this.spreadsheetApp.getSheets();
            const titles = sheets.filter(
                (title) => title.getSheetId() !== this.sheetId
            );
            this.properties.titles = titles;
        }
    }

    setNamedRanges() {
        const rangeHeader = this.sheet.getRange(1, 1);
        const numRows = this.properties.titles.length;
        const rangeToc = this.sheet.getRange(2, 1, numRows);
        this.spreadsheetApp.setNamedRange(
            this.properties.range.headerName,
            rangeHeader
        );
        this.spreadsheetApp.setNamedRange(
            this.properties.range.tocName,
            rangeToc
        );
    }
}

onOpen.gs

Code:
function onOpen() {
    const uI = new UiUtil(getSpreadsheetApp);
    uI.createMenu();
}

<p>I've been having a problem with class logic pattern in my google apps script project, and I'm not sure if it's my logica, .gs file arrangement, or if this is just unexpected behavior with classes in the google V8 engine. Nonetheless, I cannot figure out why this code won't run. It's a google apps script project attached to a spreadsheet:</p>
<p>(<a href="https://docs.google.com/spreadsheets/d/120mXQ5v4XtBeUQGAv57WkXuFhZBGwn6mywEY5vGU_Nw/edit?usp=sharing" rel="nofollow noreferrer">https://docs.google.com/spreadsheets/d/120mXQ5v4XtBeUQGAv57WkXuFhZBGwn6mywEY5vGU_Nw/edit?usp=sharing</a>)</p>
<p>For context, I am trying to understand dependency injection and coupling. I created a class, "TocSheet.gs" that manages a table of contents, and of course relies on Spreadsheet App for functionality as well as PropertiesService to store the state of a TocSheet instance. Any insight would be greatly appreciated, the link is available to the public. To access the code, click Extensions > Apps Script. Thanks for any help you can offer.</p>
<p><strong>Localized issue:</strong>
In the TocSheet.gs file, you will see that I'm attempting to inject two
"dependencies" into the class TocSheet constructor as parameters (spreadsheet App: new SpreadsheetUtil() and propsService: new PropertiesServiceStorage()). The execution log for some reason shows that the SpreadsheetUtil object was loaded into the spreadsheetApp variable and I'm not sure why.</p>
<p>UiManager.gs</p>
<pre><code>class UiUtil {
constructor(spreadsheet) {
this.uI = spreadsheet().getUi();
}

alert(message) {
return this.uI.alert(message);
}

createMenu() {
return this.uI
.createAddonMenu()
.addItem("Insert", "start")
.addSeparator()
.addItem("Remove", "confirmDelete")
.addToUi();
}

confirmDelete() {
const response = this.uI.alert(
"Confirm Deletion",
"Delete this item?",
this.ui.ButtonSet.OK_CANCEL
);

if (response) {
return true;
} else {
this.uI.alert("Deletion Cancelled");
}
return false;
}
}

function start() {
const uI = getSpreadsheetApp().getUi();
const sheet = undefined; //JSON.parse(getPropsService().getScriptProperties().getProperty("sheet"))
//console.log("sheet from props service: ", sheet)
if (!sheet) {
const spreadsheetApp = new SpreadsheetUtility();
const propsService = new PropertiesServiceStorage();
const myToc = new TocSheet(spreadsheetApp, propsService);
uI.alert("sheet created");
console.log(myToc);
}
}

// function setFalse(){
// const doSort = false
// const tocSheet = new SheetManager("TOC", getSpreadsheetApp, getPropsService,getScriptApp);
// }

function confirmDelete() {
const isConfirmed = Ui.confirmDelete;
if (isConfirmed) {
removeToc(isConfirmed);
}
}
</code></pre>
<p>SpreadsheetUtility.gs</p>
<pre><code>class SpreadsheetUtility {
constructor() {
this.activeSheet = SpreadsheetApp.getActive();
}
insertSheet(name) {
return this.activeSheet.insertSheet(name, 0);
}

setNamedRange(name, range) {
this.activeSheet.setNamedRange(name, range);
}

getSheets() {
return this.activeSheet.getSheets();
}

getSheetByName(name) {
return this.activeSheet.getSheetByName(name);
}
}

function myFunction() {
console.log(new SpreadsheetUtility().getSheets());
}
</code></pre>
<p>PropertiesService.gs</p>
<pre><code>class PropertiesServiceStorage {
constructor() {
this.key = "tocSheet";
}

save(tocData) {
const data = JSON.stringify(tocData);
PropertiesService.getScriptProperties().setProperty(this.key, data);
}

load() {
const data = PropertiesService.getScriptProperties().getProperty(
this.key
);
if (!data) {
return null;
}
return JSON.parse(data);
}
}
</code></pre>
<p>TocSheet.gs</p>
<pre><code>class TocSheet {
constructor(name, spreadsheetApp, propsService) {
console.log("From Toc: Spreadsheet: ", spreadsheetApp, propsService);

this.name = name || "Table of Contents";
this.spreadsheetApp = spreadsheetApp || new SpreadsheetUtility();
this.propsService = propsService;
console.log("spreadsheet property: ", this.spreadsheetApp);
this.propsService = propsService || new PropertiesServiceStorage();
this.properties = {
name: this.name,
sheetId: null,
titles: null,
range: {
headerName: "TOCHeader",
tocName: "TOC",
},
};
this.initialize();
}

static load() {
const storage = new PropertiesServiceStorage();
storage.load();
}

setNamedRanges() {
const numRows = this.titles.length;
this.sheet.setNamedRange(this.range.headerName, sheet.getRange(1, 1));
this.sheet.setNamedRange(
this.range.tocName,
sheet.getRange(2, 1, numRows)
);
}

initialize() {
this.createSheet();
this.setSheetId();
this.setTitles();
this.setNamedRanges();
//setTitleLinks();
}

createSheet() {
console.log("name", this.name);
const sheet = this.spreadsheetApp.insertSheet("Table of Contents");
this.sheet = sheet;
}

setSheetId() {
this.properties.sheetId = this.sheet.getSheetId();
}

setTitles(titles = null) {
if (titles) {
this.properties.titles = titles;
} else {
const sheets = this.spreadsheetApp.getSheets();
const titles = sheets.filter(
(title) => title.getSheetId() !== this.sheetId
);
this.properties.titles = titles;
}
}

setNamedRanges() {
const rangeHeader = this.sheet.getRange(1, 1);
const numRows = this.properties.titles.length;
const rangeToc = this.sheet.getRange(2, 1, numRows);
this.spreadsheetApp.setNamedRange(
this.properties.range.headerName,
rangeHeader
);
this.spreadsheetApp.setNamedRange(
this.properties.range.tocName,
rangeToc
);
}
}
</code></pre>
<p>onOpen.gs</p>
<pre><code>function onOpen() {
const uI = new UiUtil(getSpreadsheetApp);
uI.createMenu();
}
</code></pre>
 
Top