Makros in andere Google-Tabellen kopieren

Programmierkenntnisse: Mittelstufe 
Dauer: 30 Minuten 
Projekttyp: Google Workspace-Add-on

Ziele

  • Verstehen, was die Lösung bewirkt.
  • Informationen zur Funktionsweise der Apps Script-Dienste in der Lösung.
  • Umgebung einrichten
  • Richten Sie das Skript ein.
  • Führen Sie das Skript aus.

Informationen zu dieser Lösung

Das manuelle Kopieren von Google Sheets-Makros von einer Tabelle in eine andere kann zeitaufwendig und fehleranfällig sein. Dieses Google Workspace-Add‑on kopiert automatisch ein Scriptprojekt und hängt es an eine vom Nutzer angegebene Tabelle an. Diese Lösung konzentriert sich zwar auf Sheets-Makros, Sie können sie aber verwenden, um beliebige containergebundene Skripts zu kopieren und freizugeben.

Screenshot des Google Workspace-Add‑ons „Makro teilen“

Funktionsweise

Das Skript kopiert das Apps Script-Projekt, das an die ursprüngliche Tabelle gebunden ist, und erstellt ein doppeltes Apps Script-Projekt, das an die vom Nutzer angegebene Tabelle gebunden ist.

Apps Script-Dienste

Für diese Lösung werden die folgenden Dienste verwendet:

  • URL Fetch-Dienst: Stellt eine Verbindung zur Apps Script API her, um das Quellprojekt zu kopieren und eine Kopie zu erstellen.
  • Script-Dienst: Autorisieren Sie die Apps Script API, um eine zweite Autorisierungsaufforderung zu vermeiden.
  • Google Sheets: Hiermit wird die Zieltabellenkalkulation geöffnet, in die das kopierte Apps Script-Projekt eingefügt werden soll.
  • Kartenservice: Erstellt die Benutzeroberfläche des Add-ons.

Vorbereitung

Für dieses Beispiel müssen die folgenden Voraussetzungen erfüllt sein:

Umgebung einrichten

Cloud-Projekt in der Google Cloud Console öffnen

Öffnen Sie das Cloud-Projekt, das Sie für dieses Beispiel verwenden möchten, falls es noch nicht geöffnet ist:

  1. Rufen Sie in der Google Cloud Console die Seite Projekt auswählen auf.

    Cloud-Projekt auswählen

  2. Wählen Sie das Google Cloud-Projekt aus, das Sie verwenden möchten. Klicken Sie alternativ auf Projekt erstellen und folgen Sie der Anleitung auf dem Bildschirm. Wenn Sie ein Google Cloud-Projekt erstellen, müssen Sie möglicherweise die Abrechnung für das Projekt aktivieren.

Google Apps Script API aktivieren

In dieser Kurzanleitung wird die Google Apps Script API verwendet.

Bevor Sie Google APIs verwenden können, müssen Sie sie in einem Google Cloud-Projekt aktivieren. Sie können eine oder mehrere APIs in einem einzelnen Google Cloud-Projekt aktivieren.
  • Aktivieren Sie in Ihrem Cloud-Projekt die Google Apps Script API.

    API aktivieren

Für Google Workspace-Add-ons ist eine Konfiguration des Einwilligungsbildschirms erforderlich. Wenn Sie den OAuth-Zustimmungsbildschirm Ihres Add-ons konfigurieren, legen Sie fest, was Google den Nutzern anzeigt.

  1. Rufen Sie in der Google Cloud Console das Menü  > Google Auth platform > Branding auf.

    Zu Branding

  2. Wenn Sie die Google Auth platformbereits konfiguriert haben, können Sie die folgenden Einstellungen für den OAuth-Zustimmungsbildschirm unter Branding, Zielgruppe und Datenzugriff konfigurieren. Wenn Sie die Meldung Google Auth platform noch nicht konfiguriert sehen, klicken Sie auf Jetzt starten:
    1. Geben Sie unter App-Informationen im Feld App-Name einen Namen für die App ein.
    2. Wählen Sie unter E-Mail-Adresse des Nutzersupports eine Support-E-Mail-Adresse aus, über die Nutzer Sie mit Fragen zu ihrer Einwilligung kontaktieren können.
    3. Klicken Sie auf Weiter.
    4. Wählen Sie unter Zielgruppe die Option Intern aus.
    5. Klicken Sie auf Weiter.
    6. Geben Sie unter Kontaktdaten eine E-Mail-Adresse ein, unter der Sie über Änderungen an Ihrem Projekt benachrichtigt werden können.
    7. Klicken Sie auf Weiter.
    8. Sehen Sie sich unter Abschließen die Nutzerdatenrichtlinie für Google API-Dienste an. Wenn Sie damit einverstanden sind, wählen Sie Ich stimme der Nutzerdatenrichtlinie für Google API-Dienste zu aus.
    9. Klicken Sie auf Weiter.
    10. Klicken Sie auf Erstellen.
  3. Sie können das Hinzufügen von Bereichen vorerst überspringen. Wenn Sie in Zukunft eine App für die Verwendung außerhalb Ihrer Google Workspace-Organisation erstellen, müssen Sie den Nutzertyp in Extern ändern. Fügen Sie dann die Autorisierungsbereiche hinzu, die für Ihre App erforderlich sind. Weitere Informationen finden Sie in der vollständigen Anleitung OAuth-Zustimmung konfigurieren.

Skript einrichten

Apps Script-Projekt erstellen

  1. Klicken Sie auf die folgende Schaltfläche, um das Apps Script-Projekt Makro freigeben zu öffnen.
    Projekt öffnen
  2. Klicken Sie auf Übersicht .
  3. Klicken Sie auf der Übersichtsseite auf „Kopie erstellen“ Symbol zum Erstellen einer Kopie.

Cloud-Projektnummer kopieren

  1. Rufen Sie in der Google Cloud Console das Menü  > IAM & Verwaltung > Einstellungen auf.

    Weiter zur Seite „IAM & Verwaltung“

  2. Kopieren Sie den Wert aus dem Feld Projektnummer.

Cloud-Projekt für das Apps Script-Projekt festlegen

  1. Klicken Sie in Ihrem kopierten Apps Script-Projekt auf Projekteinstellungen Symbol für Projekteinstellungen.
  2. Klicken Sie unter Google Cloud Platform-Projekt (GCP-Projekt) auf Projekt ändern.
  3. Fügen Sie die Google Cloud-Projektnummer in GCP-Projektnummer ein.
  4. Klicken Sie auf Projekt festlegen.

Test-Deployment installieren

  1. Klicken Sie in Ihrem kopierten Apps Script-Projekt auf Editor .
  2. Öffnen Sie die Datei UI.gs und klicken Sie auf Ausführen. Autorisieren Sie das Skript, wenn Sie dazu aufgefordert werden.
  3. Klicken Sie auf Bereitstellen > Testbereitstellungen.
  4. Klicken Sie auf Installieren > Fertig.

Makroskript und Tabelleninformationen abrufen

  1. Öffnen Sie eine Google-Tabelle mit einem Makro, die Sie bearbeiten dürfen. Wenn Sie eine Beispieltabelle verwenden möchten, erstellen Sie eine Kopie der Tabelle Beispielmakro.
  2. Klicken Sie auf Erweiterungen > Apps Script.
  3. Klicken Sie im Apps Script-Projekt auf Projekteinstellungen Symbol für Projekteinstellungen.
  4. Klicken Sie unter der Skript-ID auf Kopieren.
  5. Notieren Sie sich die Skript-ID für einen späteren Schritt.
  6. Öffnen Sie eine vorhandene oder erstellen Sie eine neue Tabelle, in die Sie das Makro einfügen möchten. Sie müssen berechtigt sein, das Tabellenblatt zu bearbeiten.
  7. Kopieren Sie die Tabellen-URL und legen Sie sie für einen späteren Schritt beiseite.

Skript ausführen

Achten Sie darauf, dass die Google Apps Script API in Ihren Dashboard-Einstellungen aktiviert ist. Führen Sie die Schritte in den folgenden Abschnitten aus, um Ihr Skript auszuführen.

Makro kopieren

  1. Öffnen Sie in Google Sheets in der rechten Seitenleiste das Add-on „Makro teilen“ Symbol zum Erstellen einer Kopie.
  2. Fügen Sie unter Quellmakro die Skript-ID ein.
  3. Fügen Sie unter Zieltabelle die Tabellen-URL ein.
  4. Klicken Sie auf Makro freigeben.
  5. Klicken Sie auf Zugriff autorisieren und autorisieren Sie das Add-on.
  6. Wiederholen Sie die Schritte 2 bis 4.

Kopiertes Makro öffnen

  1. Öffnen Sie die Tabelle, in die Sie das Makro kopiert haben, falls sie noch nicht geöffnet ist.
  2. Klicken Sie auf Erweiterungen > Apps Script.
  3. Wenn Sie das kopierte Apps Script-Projekt nicht sehen, prüfen Sie, ob die Google Apps Script API in den Dashboard-Einstellungen aktiviert ist, und wiederholen Sie die Schritte unter Makro kopieren.

Code ansehen

Wenn Sie den Apps Script-Code für diese Lösung ansehen möchten, klicken Sie unten auf Quellcode ansehen:

Quellcode ansehen

Code.gs

solutions/add-on/share-macro/Code.js
// To learn how to use this script, refer to the documentation:
// https://developers.devsite.corp.google.com/apps-script/add-ons/share-macro

/*
Copyright 2022 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Uses Apps Script API to copy source Apps Script project 
 * to destination Google Spreadsheet container.
 * 
 * @param {string} sourceScriptId - Script ID of the source project.
 * @param {string} targetSpreadsheetUrl - URL if the target spreadsheet.
 */
function shareMacro_(sourceScriptId, targetSpreadsheetUrl) {

  // Gets the source project content using the Apps Script API.
  const sourceProject = APPS_SCRIPT_API.get(sourceScriptId);
  const sourceFiles = APPS_SCRIPT_API.getContent(sourceScriptId);

  // Opens the target spreadsheet and gets its ID.
  const parentSSId = SpreadsheetApp.openByUrl(targetSpreadsheetUrl).getId();

  // Creates an Apps Script project that's bound to the target spreadsheet.
  const targetProjectObj = APPS_SCRIPT_API.create(sourceProject.title, parentSSId);

  // Updates the Apps Script project with the source project content.
  APPS_SCRIPT_API.updateContent(targetProjectObj.scriptId, sourceFiles);

}

/**
 * Function that encapsulates Apps Script API project manipulation. 
*/
const APPS_SCRIPT_API = {
  accessToken: ScriptApp.getOAuthToken(),

  /* APPS_SCRIPT_API.get
   * Gets Apps Script source project.
   * @param {string} scriptId - Script ID of the source project.
   * @return {Object} - JSON representation of source project.
   */
  get: function (scriptId) {
    const url = ('https://script.googleapis.com/v1/projects/' + scriptId);
    const options = {
      "method": 'get',
      "headers": {
        "Authorization": "Bearer " + this.accessToken
      },
      "muteHttpExceptions": true,
    };
    const res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      return JSON.parse(res);
    } else {
      console.log('An error occurred gettting the project details');
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  },

  /* APPS_SCRIPT_API.create
   * Creates new Apps Script project in the target spreadsheet.
   * @param {string} title - Name of Apps Script project.
   * @param {string} parentId - Internal ID of target spreadsheet.
   * @return {Object} - JSON representation completed project creation.
   */
  create: function (title, parentId) {
    const url = 'https://script.googleapis.com/v1/projects';
    const options = {
      "headers": {
        "Authorization": "Bearer " + this.accessToken,
        "Content-Type": "application/json"
      },
      "muteHttpExceptions": true,
      "method": "POST",
      "payload": { "title": title }
    }
    if (parentId) {
      options.payload.parentId = parentId;
    }
    options.payload = JSON.stringify(options.payload);
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      res = JSON.parse(res);
      return res;
    } else {
      console.log("An error occurred while creating the project");
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  },
   /* APPS_SCRIPT_API.getContent
   * Gets the content of the source Apps Script project.
   * @param {string} scriptId - Script ID of the source project.
   * @return {Object} - JSON representation of Apps Script project content.
   */
   getContent: function (scriptId) {
    const url = "https://script.googleapis.com/v1/projects/" + scriptId + "/content";
    const options = {
      "method": 'get',
      "headers": {
        "Authorization": "Bearer " + this.accessToken
      },
      "muteHttpExceptions": true,
    };
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      res = JSON.parse(res);
      return res['files'];
    } else {
      console.log('An error occurred obtaining the content from the source script');
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  },

  /* APPS_SCRIPT_API.updateContent
   * Updates (copies) content from source to target Apps Script project.
   * @param {string} scriptId - Script ID of the source project.
   * @param {Object} files - JSON representation of Apps Script project content.
   * @return {boolean} - Result status of the function.
   */
  updateContent: function (scriptId, files) {
    const url = "https://script.googleapis.com/v1/projects/" + scriptId + "/content";
    const options = {
      "method": 'put',
      "headers": {
        "Authorization": "Bearer " + this.accessToken
      },
      "contentType": "application/json",
      "payload": JSON.stringify({ "files": files }),
      "muteHttpExceptions": true,
    };
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      return true;
    } else {
      console.log(`An error occurred updating content of script ${scriptId}`);
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  }
}

UI.gs

solutions/add-on/share-macro/UI.js
/**
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Change application logo here (and in manifest) as desired.
const ADDON_LOGO = 'https://www.gstatic.com/images/branding/product/2x/apps_script_48dp.png';

/**
 * Callback function for rendering the main card.
 * @return {CardService.Card} The card to show the user.
 */
function onHomepage(e) {
  return createSelectionCard(e);
}

/**
 * Builds the primary card interface used to collect user inputs.
 * 
 * @param {Object} e - Add-on event object.
 * @param {string} sourceScriptId - Script ID of the source project.
 * @param {string} targetSpreadsheetUrl - URL of the target spreadsheet.
 * @param {string[]} errors - Array of error messages. 
 * 
 * @return {CardService.Card} The card to show to the user for inputs.
 */
function createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, errors) {

  // Configures card header.
  let cardHeader = CardService.newCardHeader()
    .setTitle('Share macros with other spreadheets!')
    .setImageUrl(ADDON_LOGO)
    .setImageStyle(CardService.ImageStyle.SQUARE);

  // If form errors exist, configures section with error messages.
  let showErrors = false;

  if (errors && errors.length) {
    showErrors = true;
    let msg = errors.reduce((str, err) => `${str}${err}<br>`, '');
    msg = `<b>Form submission errors:</b><br><font color="#ba0000">${msg}</font>`;

    // Builds error message section.
    sectionErrors = CardService.newCardSection()
      .addWidget(CardService.newDecoratedText()
        .setText(msg)
        .setWrapText(true));
  }

  // Configures source project section.
  let sectionSource = CardService.newCardSection()
    .addWidget(CardService.newDecoratedText()
      .setText('<b>Source macro</b><br>The Apps Script project to copy'))

    .addWidget(CardService.newTextInput()
      .setFieldName('sourceScriptId')
      .setValue(sourceScriptId || '')
      .setTitle('Script ID of the source macro')
      .setHint('You must have at least edit permission for the source spreadsheet to access its script project'))

    .addWidget(CardService.newTextButton()
      .setText('Find the script ID')
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://developers.google.com/apps-script/api/samples/execute')
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.NOTHING)));

  // Configures target spreadsheet section.
  let sectionTarget = CardService.newCardSection()
    .addWidget(CardService.newDecoratedText()
      .setText('<b>Target spreadsheet</b>'))

    .addWidget(CardService.newTextInput()
      .setFieldName('targetSpreadsheetUrl')
      .setValue(targetSpreadsheetUrl || '')
      .setHint('You must have at least edit permission for the target spreadsheet')
      .setTitle('Target spreadsheet URL'));

  // Configures help section.
  let sectionHelp = CardService.newCardSection()
    .addWidget(CardService.newDecoratedText()
      .setText('<b><font color=#c80000>NOTE: </font></b>' +
        'The Apps Script API must be turned on.')
      .setWrapText(true))

    .addWidget(CardService.newTextButton()
      .setText('Turn on Apps Script API')
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://script.google.com/home/usersettings')
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.NOTHING)));

  // Configures card footer with action to copy the macro.
  var cardFooter = CardService.newFixedFooter()
    .setPrimaryButton(CardService.newTextButton()
      .setText('Share macro')
      .setOnClickAction(CardService.newAction()
        .setFunctionName('onClickFunction_')));

  // Begins building the card.
  let builder = CardService.newCardBuilder()
    .setHeader(cardHeader);

  // Adds error section if applicable.
  if (showErrors) {
    builder.addSection(sectionErrors)
  }

  // Adds final sections & footer.
  builder
    .addSection(sectionSource)
    .addSection(sectionTarget)
    .addSection(sectionHelp)
    .setFixedFooter(cardFooter);

  return builder.build();
}

/**
 * Action handler that validates user inputs and calls shareMacro_
 * function to copy Apps Script project to target spreadsheet.
 * 
 * @param {Object} e - Add-on event object.
 * 
 * @return {CardService.Card} Responds with either a success or error card.
 */
function onClickFunction_(e) {

  const sourceScriptId = e.formInput.sourceScriptId;
  const targetSpreadsheetUrl = e.formInput.targetSpreadsheetUrl;

  // Validates inputs for errors.
  const errors = [];

  // Pushes an error message if the Script ID parameter is missing.
  if (!sourceScriptId) {
    errors.push('Missing script ID');
  } else {

    // Gets the Apps Script project if the Script ID parameter is valid.
    const sourceProject = APPS_SCRIPT_API.get(sourceScriptId);
    if (!sourceProject) {
      // Pushes an error message if the Script ID parameter isn't valid.
      errors.push('Invalid script ID');
    }
  }

  // Pushes an error message if the spreadsheet URL is missing.
  if (!targetSpreadsheetUrl) {
    errors.push('Missing Spreadsheet URL');
  } else
    try {
      // Tests for valid spreadsheet URL to get the spreadsheet ID.
      const ssId = SpreadsheetApp.openByUrl(targetSpreadsheetUrl).getId();
    } catch (err) {
      // Pushes an error message if the spreadsheet URL parameter isn't valid.
      errors.push('Invalid spreadsheet URL');
    }

  if (errors && errors.length) {
    // Redisplays form if inputs are missing or invalid.
    return createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, errors);

  } else {
    // Calls shareMacro function to copy the project.
    shareMacro_(sourceScriptId, targetSpreadsheetUrl);

    // Creates a success card to display to users.
    return buildSuccessCard(e, targetSpreadsheetUrl);
  }
}

/**
 * Builds success card to inform user & let them open the spreadsheet.
 * 
 * @param {Object} e - Add-on event object.
 * @param {string} targetSpreadsheetUrl - URL of the target spreadsheet.
 * 
 * @return {CardService.Card} Returns success card.
 */function buildSuccessCard(e, targetSpreadsheetUrl) {

  // Configures card header.
  let cardHeader = CardService.newCardHeader()
    .setTitle('Share macros with other spreadsheets!')
    .setImageUrl(ADDON_LOGO)
    .setImageStyle(CardService.ImageStyle.SQUARE);

  // Configures card body section with success message and open button.
  let sectionBody1 = CardService.newCardSection()
    .addWidget(CardService.newTextParagraph()
      .setText('Sharing process is complete!'))
    .addWidget(CardService.newTextButton()
      .setText('Open spreadsheet')
      .setOpenLink(CardService.newOpenLink()
        .setUrl(targetSpreadsheetUrl)
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.RELOAD_ADD_ON)));
  let sectionBody2 = CardService.newCardSection()
    .addWidget(CardService.newTextParagraph()
      .setText('If you don\'t see the copied project in your target spreadsheet,' +
       ' make sure you turned on the Apps Script API in the Apps Script dashboard.'))
    .addWidget(CardService.newTextButton()
      .setText("Check API")
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://script.google.com/home/usersettings')
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.RELOAD_ADD_ON)));

  // Configures the card footer with action to start new process.
  let cardFooter = CardService.newFixedFooter()
    .setPrimaryButton(CardService.newTextButton()
      .setText('Share another')
      .setOnClickAction(CardService.newAction()
        .setFunctionName('onHomepage')));

  return builder = CardService.newCardBuilder()
    .setHeader(cardHeader)
    .addSection(sectionBody1)
    .addSection(sectionBody2)
    .setFixedFooter(cardFooter)
    .build();
 }

appsscript.json

solutions/add-on/share-macro/appsscript.json
{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/script.projects"
  ],
    "urlFetchWhitelist": [
    "https://script.googleapis.com/"
  ],
  "addOns": {
    "common": {
      "name": "Share Macro",
      "logoUrl": "https://www.gstatic.com/images/branding/product/2x/apps_script_48dp.png",
      "layoutProperties": {
        "primaryColor": "#188038",
        "secondaryColor": "#34a853"
      },
      "homepageTrigger": {
        "runFunction": "onHomepage"
      }
    },
    "sheets": {}
  }
}

Beitragende

Dieses Beispiel wird von Google mit Unterstützung von Google Developer Experts verwaltet.

Nächste Schritte