คัดลอกมาโครไปยังสเปรดชีตอื่น

ระดับการเขียนโค้ด: ระดับกลาง
ระยะเวลา: 30 นาที
ประเภทโปรเจ็กต์: ส่วนเสริมของ Google Workspace

วัตถุประสงค์

  • ทำความเข้าใจสิ่งที่โซลูชันทำ
  • ทำความเข้าใจสิ่งที่บริการ Apps Script ทําภายใน โซลูชัน
  • ตั้งค่าสภาพแวดล้อม
  • ตั้งค่าสคริปต์
  • เรียกใช้สคริปต์

เกี่ยวกับโซลูชันนี้

การคัดลอกมาโครของ Google ชีตจากสเปรดชีตหนึ่งไปยังอีกสเปรดชีตหนึ่งสามารถทำได้ด้วยตนเอง ใช้เวลามากและเกิดข้อผิดพลาดได้ง่าย ส่วนเสริมของ Google Workspace นี้โดยอัตโนมัติ จะคัดลอกโครงการสคริปต์และแนบไปกับสเปรดชีตที่ผู้ใช้ระบุ แม้ว่า โซลูชันนี้มุ่งเน้นที่มาโครของชีตซึ่งคุณสามารถใช้เพื่อคัดลอก และแชร์สคริปต์ที่ผูกกับคอนเทนเนอร์

ภาพหน้าจอของส่วนเสริมการแชร์มาโครของ Google Workspace

วิธีการทำงาน

สคริปต์จะคัดลอกโครงการสคริปต์ Apps ที่เชื่อมโยงกับ สเปรดชีตต้นฉบับและสร้างโปรเจ็กต์ Apps Script ที่ซ้ำกัน เชื่อมโยงกับสเปรดชีตที่ผู้ใช้ระบุ

บริการ Apps Script

โซลูชันนี้ใช้บริการต่อไปนี้

ข้อกำหนดเบื้องต้น

หากต้องการใช้ตัวอย่างนี้ คุณต้องมีข้อกำหนดเบื้องต้นต่อไปนี้

ตั้งค่าสภาพแวดล้อมของคุณ

เปิดโปรเจ็กต์ที่อยู่ในระบบคลาวด์ในคอนโซล Google Cloud

หากยังไม่ได้เปิด ให้เปิดโปรเจ็กต์ที่อยู่ในระบบคลาวด์ที่ต้องการใช้ สำหรับตัวอย่างนี้

  1. ในคอนโซล Google Cloud ให้ไปที่หน้าเลือกโปรเจ็กต์

    เลือกโปรเจ็กต์ที่อยู่ในระบบคลาวด์

  2. เลือกโปรเจ็กต์ Google Cloud ที่ต้องการใช้ หรือคลิกสร้างโปรเจ็กต์แล้วทำตามวิธีการบนหน้าจอ หากสร้างโปรเจ็กต์ Google Cloud คุณอาจต้องเปิดการเรียกเก็บเงินสำหรับโปรเจ็กต์

เปิด Google Apps Script API

การเริ่มต้นอย่างรวดเร็วนี้ใช้ Google Apps Script API

ก่อนใช้ Google APIs คุณต้องเปิดใช้ API เหล่านี้ในโปรเจ็กต์ Google Cloud คุณสามารถเปิด API ได้ตั้งแต่ 1 รายการขึ้นไปในโปรเจ็กต์ Google Cloud เดียว
  • เปิด Google Apps Script API ในโปรเจ็กต์ที่อยู่ในระบบคลาวด์

    เปิด API

ส่วนเสริมของ Google Workspace ต้องมีการกำหนดค่าหน้าจอขอความยินยอม กำลังกำหนดค่า หน้าจอคำยินยอม OAuth ของส่วนเสริมจะระบุสิ่งที่ Google แสดงต่อผู้ใช้

  1. ในคอนโซล Google Cloud ให้ไปที่เมนู > API และ บริการ > หน้าจอขอความยินยอม OAuth

    ไปที่หน้าจอขอความยินยอม OAuth

  2. เลือกประเภทผู้ใช้เป็นภายใน แล้วคลิกสร้าง
  3. กรอกแบบฟอร์มการลงทะเบียนแอป แล้วคลิกบันทึกและดำเนินการต่อ
  4. ในตอนนี้ คุณสามารถข้ามการเพิ่มขอบเขต แล้วคลิกบันทึกและดำเนินการต่อได้ ในอนาคต เมื่อคุณสร้างแอปสำหรับใช้นอก ในองค์กร Google Workspace คุณต้องเปลี่ยนประเภทผู้ใช้เป็นภายนอก จากนั้น เพิ่มขอบเขตการให้สิทธิ์ที่แอปของคุณต้องการ

  5. ตรวจสอบสรุปการลงทะเบียนแอป หากต้องการเปลี่ยนแปลง ให้คลิกแก้ไข หากแอป การลงทะเบียนถูกต้องแล้ว ให้คลิกกลับไปที่หน้าแดชบอร์ด

ตั้งค่าสคริปต์

สร้างโปรเจ็กต์ Apps Script

  1. คลิกปุ่มต่อไปนี้เพื่อเปิดแชร์มาโคร โครงการ Apps Script
    เปิดโครงการ
  2. คลิกภาพรวม
  3. ในหน้าภาพรวม ให้คลิกทำสำเนา ไอคอนสำหรับการทำสำเนา

คัดลอกหมายเลขโปรเจ็กต์ที่อยู่ในระบบคลาวด์

  1. ในคอนโซล Google Cloud ให้ไปที่เมนู > IAM และ ผู้ดูแลระบบ > การตั้งค่า

    ไปที่ IAM และ การตั้งค่าของผู้ดูแลระบบ

  2. คัดลอกค่าในช่องหมายเลขโปรเจ็กต์

ตั้งค่าโปรเจ็กต์ที่อยู่ในระบบคลาวด์ของโปรเจ็กต์ Apps Script

  1. ในโปรเจ็กต์ Apps Script ที่คัดลอกมา คลิกการตั้งค่าโปรเจ็กต์ ไอคอนสำหรับการตั้งค่าโปรเจ็กต์
  2. ในส่วนโครงการ Google Cloud Platform (GCP) ให้คลิกเปลี่ยนโครงการ
  3. วางหมายเลขโปรเจ็กต์ Google Cloud ในหมายเลขโปรเจ็กต์ GCP
  4. คลิกตั้งค่าโปรเจ็กต์

ติดตั้งการทดสอบการทำให้ใช้งานได้

  1. คลิกตัวแก้ไขในโปรเจ็กต์ Apps Script ที่คัดลอกมา
  2. เปิดไฟล์ UI.gs แล้วคลิกเรียกใช้ เมื่อได้รับข้อความแจ้ง ให้สิทธิ์ สคริปต์
  3. คลิกทำให้ใช้งานได้ > ทดสอบการทำให้ใช้งานได้
  4. คลิกติดตั้ง > เสร็จสิ้น

รับสคริปต์มาโครและข้อมูลสเปรดชีต

  1. เปิดสเปรดชีตของชีตที่มีมาโครและคุณมีสิทธิ์ แก้ไข หากต้องการใช้สเปรดชีตตัวอย่าง ให้ทำสำเนามาโครตัวอย่าง สเปรดชีต
  2. คลิกส่วนขยาย > Apps Script
  3. ในโปรเจ็กต์ Apps Script ให้คลิก การตั้งค่าโปรเจ็กต์ ไอคอนสำหรับการตั้งค่าโปรเจ็กต์
  4. คลิกคัดลอกใต้รหัสสคริปต์
  5. เตรียมรหัสสคริปต์ไว้ต่างหากเพื่อใช้ในขั้นตอนถัดไป
  6. เปิดหรือสร้างสเปรดชีตใหม่ที่ต้องการเพิ่มมาโคร คุณต้อง มีสิทธิ์แก้ไขสเปรดชีต
  7. คัดลอก URL ของสเปรดชีตและวางไว้ในขั้นตอนถัดไป

เรียกใช้สคริปต์

โปรดตรวจสอบว่า Google Apps Script API เปิดอยู่ในการตั้งค่าหน้าแดชบอร์ด ทำตามขั้นตอนในส่วนต่อไปนี้เพื่อเรียกใช้สคริปต์

คัดลอกมาโคร

  1. ในชีต ให้เปิดส่วนเสริมมาโครการแชร์ที่แถบด้านขวา ไอคอนสำหรับการทำสำเนา
  2. วางรหัสสคริปต์ในส่วนมาโครแหล่งที่มา
  3. วาง URL ของสเปรดชีตในสเปรดชีตเป้าหมาย
  4. คลิกแชร์มาโคร
  5. คลิกให้สิทธิ์เข้าถึงและให้สิทธิ์ส่วนเสริม
  6. ทำขั้นตอนที่ 2-4 ซ้ำ

เปิดมาโครที่คัดลอก

  1. หากยังไม่ได้เปิดสเปรดชีต ให้เปิดสเปรดชีตที่คุณคัดลอกมาโครไว้
  2. คลิกส่วนขยาย > Apps Script
  3. หากไม่เห็นโครงการ Apps Script ที่คัดลอกไว้ ให้ตรวจสอบว่า มีการเปิด Google Apps Script API ในหน้าแดชบอร์ด การตั้งค่า แล้วทำตามขั้นตอนที่แสดงในส่วนคัดลอกมาโครซ้ำ

ตรวจสอบโค้ด

หากต้องการตรวจสอบโค้ด Apps Script สำหรับโซลูชันนี้ ให้คลิก ดูซอร์สโค้ดด้านล่าง

ดูซอร์สโค้ด

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": {}
  }
}

ผู้ร่วมให้ข้อมูล

ตัวอย่างนี้ดูแลโดย Google ด้วยความช่วยเหลือจากผู้เชี่ยวชาญของ Google Developer

ขั้นตอนถัดไป