How can an App Script detect if the onOpen method is called with a Doc, Sheet, or other type of document?
Asked Answered
B

4

8

In an onOpen method, how can the document type be determined?

From the Quickstart: Add-on for Google Docs the following code is suggested:

function onOpen(e) {
  DocumentApp.getUi().createAddonMenu()
      .addItem('Start', 'showSidebar')
      .addToUi(); 
}

However, when a Google Sheet is opened the script throws an exception:

Exception: Cannot call DocumentApp.getUi() from this context. at onOpen(Code:9:15)

There should be a test, first, detecting the document type context that is being opened, allowing the script to select if and how it will add menu items. How to do that? The reference for onOpen indicates e.source will be a different type, but type of e.source is only object.

Desire is something like:

function onOpen(e) {
  if (/* answer to this question: test if onOpen called for Doc only */) {
    DocumentApp.getUi().createAddonMenu()
        .addItem('Start', 'showSidebar')
        .addToUi(); 
  }
}
Bougainville answered 26/8, 2019 at 4:38 Comment(0)
S
5
  • You want to detect the mimeType of Google Docs when the Google Docs is opened. Then, you want to retrieve the object of doc.
  • You want to achieve this using Google Apps Script.

If my understanding is correct, how about this answer? Please think of this as just one of several answers.

Solution:

In this sample script, it retrieves the active document of the container-bound script when the Google Docs is opened. As the sample situation, when this script is used for the container-bound script of Spreadsheet, DocumentApp.getActiveDocument(), SlidesApp.getActivePresentation() and FormApp.getActiveForm() return null. And only SpreadsheetApp.getActiveSpreadsheet() returns the object. This method uses this situation.

Sample script:

The sample script is as follows.

function onOpen() {
  var docObject = DocumentApp.getActiveDocument() ? DocumentApp :
      SpreadsheetApp.getActiveSpreadsheet() ? SpreadsheetApp :
      SlidesApp.getActivePresentation() ? SlidesApp :
      FormApp.getActiveForm() ? FormApp : null;

  // When this is used for your script, it becomes as follows.
  docObject.getUi().createAddonMenu()
        .addItem('Start', 'showSidebar')
        .addToUi();
}
  • For example, when above script is put to Google Document, docObject becomes the object of DocumentApp. And docObject.getUi().createAddonMenu().addItem('Start', 'showSidebar').addToUi() works for the Google Document.

Note:

  • In this script, the following 4 scopes are used.
    • https://www.googleapis.com/auth/documents
    • https://www.googleapis.com/auth/forms
    • https://www.googleapis.com/auth/presentations
    • https://www.googleapis.com/auth/spreadsheets
  • For example, if you are not required to check the Google Form, please remove FormApp.getActiveForm() ? FormApp :.
  • Above script works by the simple trigger. But if you add other methods, it might required to use the installable trigger. Please be careful this.

References:

If I misunderstood your question and this was not the direction you want, I apologize.

Edit:

At first, I apologize I misunderstood your goal. From your updated question, I could understand as follows.

  • You want to run the script when the parent of the container-bound script is only Google Document.

If my understanding is correct, when the method of my answer is used, how about the following modification?

From:

if (/* answer to this question: test if onOpen called for Doc only */) {

To:

if (DocumentApp.getActiveDocument()) {
  • In this case, when this is used to Google Docs except for Google Document, DocumentApp.getActiveDocument() returns null. By this, the script in the if statement is not run for Google Docs except for Google Document.
Systemic answered 26/8, 2019 at 5:21 Comment(0)
C
4

Inbuilt global variable's .toString() method usually gives the class name. You can use it to determine the document type.

function onOpen(e){
  const app = this[ e.source + "App" ];//automatically becomes SpreadsheetApp or DocumentApp or any other editor App
  app.getUi()/*Do whatever you want with the Ui object*/;
}
Cysteine answered 26/8, 2019 at 20:43 Comment(2)
Don't expect to work for SlidesApp. Something like this this[ (String(e.source) === "Presentation" ? "Slides" : e.source) + "App"] should work though.Cysteine
@Systemic Thanks :)Cysteine
O
1

Here is the function I wrote for this. The trick is you can't call any methods that access the document scope, otherwise your code will stope executing. So you need to access a function that won't trigger any AuthMode.LIMITED or FULL execution scopes.

type Host = 'docs' | 'slides';

function getHost(): Host {
  let appType;

  try {
    var activeDocument = DocumentApp.getUi();
    if (activeDocument) {
      return 'docs';
    }
  } catch (e) {
    console.log(e)
    // DocumentApp is not available.
    console.info('DocumentApp is not available.')
  }

  try {
    var activePresentation = SlidesApp.getUi();
    if (activePresentation) {
      return 'slides';
    }
  } catch (e) {
    console.log(e)
    // SlidesApp is not available.
    console.info('SlidesApp is not available.')
  }

  return appType;
}
Outrush answered 23/3, 2023 at 15:56 Comment(1)
this is only approach that properly worksDredi
D
0

The only way it works for me is using try catch operation.

function getHost() {
  let docUI;

  try {
    docUI = DocumentApp.getUi();
    if (docUI) {
      return {type:"doc", doc:DocumentApp, docUI};
    }
  } catch (e) {
    Logger.log("DocumentApp is not available.");
  }

  try {
    docUI = SpreadsheetApp.getUi();
    if (docUI) {
      return {type:"sheet", doc:SpreadsheetApp, docUI};
    }
  } catch (e) {
    Logger.log("SpreadsheetApp is not available.");
  }

  return null;
}
   
    
function onOpen(e) {
  const host = getHost();
  if(!host) 
     return;

  Logger.log("onOpen - host " + host.type);
}
Dredi answered 27/7, 2023 at 7:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.