Kopiowanie makr do innych arkuszy kalkulacyjnych

Poziom kodowania: średnio zaawansowany
Czas trwania: 30 minut
Typ projektu: dodatek do Google Workspace

Cele

  • Dowiedz się, do czego służy dane rozwiązanie.
  • dowiedzieć się, jak usługi Apps Script działają w danym rozwiązaniu.
  • Skonfiguruj środowisko.
  • Skonfiguruj skrypt.
  • Uruchom skrypt.

Informacje o rozwiązaniu

Ręczne kopiowanie makr Arkuszy Google z jednego arkusza kalkulacyjnego do innego może być czasochłonne i podatne na błędy. Ten dodatek do Google Workspace automatycznie kopiuje projekt skryptu i dołącza go do arkusza kalkulacyjnego określonego przez użytkownika. Choć to rozwiązanie koncentruje się na makrach Arkuszy, możesz za jego pomocą kopiować i udostępniać dowolny skrypt powiązany z kontenerem.

Zrzut ekranu przedstawiający dodatek do Google Workspace do udostępniania makro

Jak to działa

Skrypt kopiuje projekt Apps Script powiązany z oryginalnym arkuszem kalkulacyjnym i tworzy duplikat projektu Apps Script powiązany z arkuszem kalkulacyjnym określonym przez użytkownika.

Usługi Apps Script

To rozwiązanie korzysta z następujących usług:

Wymagania wstępne

Aby korzystać z tego przykładu, musisz spełnić te wymagania wstępne:

Konfigurowanie środowiska

Otwieranie projektu Cloud w konsoli Google Cloud

Jeśli nie jest jeszcze otwarty, otwórz projekt Cloud, którego chcesz użyć w tym przykładzie:

  1. W konsoli Google Cloud otwórz stronę Wybierz projekt.

    Wybierz projekt Cloud

  2. Wybierz projekt Google Cloud, którego chcesz użyć. Możesz też kliknąć Utwórz projekt i postępować zgodnie z instrukcjami wyświetlanymi na ekranie. Jeśli utworzysz projekt Google Cloud, może być konieczne włączenie dla niego płatności.

Włączanie interfejsu Google Apps Script API

W tym krótkim wprowadzeniu używamy interfejsu Google Apps Script API.

Zanim zaczniesz korzystać z interfejsów API Google, musisz włączyć je w projekcie Google Cloud. W jednym projekcie Google Cloud możesz włączyć 1 lub więcej interfejsów API.

Dodatki do Google Workspace wymagają konfiguracji ekranu zgody. Konfigurowanie ekranu zgody OAuth dodatku określa, co Google wyświetla użytkownikom.

  1. W konsoli Google Cloud otwórz Menu > Interfejsy API i usługi > Ekran akceptacji OAuth.

    Otwórz ekran zgody OAuth

  2. W polu Typ użytkownika wybierz Wewnętrzny i kliknij Utwórz.
  3. Wypełnij formularz rejestracji aplikacji, a następnie kliknij Zapisz i kontynuuj.
  4. Na razie możesz pominąć dodawanie zakresów i kliknąć Zapisz i kontynuuj. Gdy w przyszłości będziesz tworzyć aplikację do użytku poza organizacją Google Workspace, musisz zmienić Typ użytkownika na Zewnętrzny, a następnie dodać zakresy autoryzacji wymagane przez Twoją aplikację.

  5. Przejrzyj podsumowanie rejestracji aplikacji. Aby wprowadzić zmiany, kliknij Edytuj. Jeśli rejestracja aplikacji wygląda na prawidłową, kliknij Back to Dashboard (Powrót do panelu).

Konfigurowanie skryptu

Tworzenie projektu Apps Script

  1. Kliknij przycisk, aby otworzyć projekt Apps Script Udostępnij makro.
    Otwórz projekt
  2. Kliknij Przegląd .
  3. Na stronie przeglądu kliknij Utwórz kopię Ikona tworzenia kopii.

Kopiowanie numeru projektu Cloud

  1. W konsoli Google Cloud kliknij Menu > Administracja > Ustawienia.

    Otwórz Ustawienia Administracja

  2. Skopiuj wartość z pola Numer projektu.

Ustawianie projektu Cloud dla projektu Apps Script

  1. W skopiowanym projekcie Apps Script kliknij Ustawienia projektu Ikona ustawień projektu.
  2. W sekcji Projekt Google Cloud Platform (GCP) kliknij Zmień projekt.
  3. W polu Numer projektu GCP wklej numer projektu Google Cloud.
  4. Kliknij Ustaw projekt.

Instalowanie wdrożenia testowego

  1. W skopiowanym projekcie Apps Script kliknij Edytor .
  2. Otwórz plik UI.gs i kliknij Uruchom. Gdy pojawi się odpowiedni komunikat, autoryzuj skrypt.
  3. Kliknij Wdróż > Testuj wdrożenia.
  4. Kliknij Zainstaluj > Gotowe.

Pobieranie informacji o skrypcie makra i arkuszu kalkulacyjnym

  1. Otwórz arkusz kalkulacyjny Arkuszy, który zawiera makro i masz uprawnienia do edycji. Aby użyć przykładowego arkusza kalkulacyjnego, utwórz kopię arkusza Przykładowe makro.
  2. Kliknij Rozszerzenia > Apps Script.
  3. W projekcie Apps Script kliknij Ustawienia projektu Ikona ustawień projektu.
  4. Pod identyfikatorem skryptu kliknij Kopiuj.
  5. Zachowaj identyfikator skryptu do użycia w późniejszym kroku.
  6. Otwórz lub utwórz nowy arkusz kalkulacyjny, do którego chcesz dodać makro. Musisz mieć uprawnienia do edycji arkusza kalkulacyjnego.
  7. Skopiuj adres URL arkusza kalkulacyjnego i odłóż go do użycia w późniejszym kroku.

Uruchom skrypt

Upewnij się, że interfejs Google Apps Script API jest włączony w ustawieniach panelu. Aby uruchomić skrypt, wykonaj czynności opisane w sekcjach poniżej.

Skopiuj makro

  1. W Arkuszach na prawym pasku bocznym otwórz dodatek Udostępnij makro Ikona tworzenia kopii.
  2. W sekcji Makro źródłowe wklej identyfikator skryptu.
  3. W sekcji Docelowy arkusz kalkulacyjny wklej adres URL arkusza kalkulacyjnego.
  4. Kliknij Udostępnij makro.
  5. Kliknij Autoryzuj dostęp i autoryzuj dodatek.
  6. Powtórz kroki 2–4.

Otwórz skopiowane makro

  1. Otwórz arkusz kalkulacyjny, do którego zostało skopiowane makro, jeśli nie jest on jeszcze otwarty.
  2. Kliknij Rozszerzenia > Apps Script.
  3. Jeśli nie widzisz skopiowanego projektu Apps Script, sprawdź, czy interfejs Google Apps Script API jest włączony w ustawieniach panelu, i powtórz czynności podane w sekcji Kopiowanie makra.

Sprawdź kod

Aby sprawdzić kod Apps Script dla tego rozwiązania, kliknij Wyświetl kod źródłowy poniżej:

Pokaż kod źródłowy

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.
 * @return {Card[]} - Card indicating successful copy.
 */
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": {}
  }
}

Współtwórcy

Ta próbka jest opracowywana przez Google z pomocą Ekspertów Produktowych Google.

Dalsze kroki