Скрипт Google Apps для дополнений

Давайте расширим Google Диск с помощью Apps Script, чтобы создать простую надстройку, использовать CardService для пользовательского интерфейса, где мы выберем несколько электронных таблиц и передадим их на следующую карту с навигацией. Другие мои блоги о Google Apps Scripts вы можете найти прямо здесь.

вступление

Привет, это Нибес Хадка из салона кодирования Хадки. Я написал этот блог, потому что лично нахожу документацию слишком сложной для начинающих. Также очень сложно найти блоги на скриптах приложений Google. Итак, этот блог для начинающих был создан, чтобы вы начали. Я считаю, что этот блог даст вам почти 20%, которые вам понадобятся для завершения почти 80% ваших проектов.

Предварительное требование

Вам понадобятся знания JavaScript и доступ к Google Drive. Я использую среду сценариев приложений, но если вы хотите разрабатывать в локальной среде, вам будет полезно это руководство по настройке.

Настройки

Перейдите в приборную панель и создайте новый файл сценария для проекта. После этого нам нужно подготовить наши проекты, как указано ниже.

ГлавнаяСтраницы

Согласно документации, есть два типа домашних страниц при разработке надстроек для диска: контекстные и неконтекстные.

Неконтекстный — это начальное отображение, когда ничего не происходит, например, первый экран, отображаемый после нажатия значка надстройки. Контекстный — это домашняя страница/экран, который появляется, когда мы выполняем определенное действие, например, выбираем файлы на диске.

Чтобы функции сценариев приложений вызывались на диске, нам нужно назначить эти функции соответствующим триггерам для надстройки диска в файле манифеста (appsscript.json).

Триггеры домашней страницы

Когда пользователь нажимает на значок надстройки, вызывается метод drive.homepageTrigger. Затем этот метод ищет функцию, а затем вызывает указанную функцию в манифесте (appsscript.json) для дальнейших операций.

Триггер выбранного элемента

Для контекстных триггеров мы назначим функцию в сценарии нашего приложения drive.onItemSelectedTrigger в файле манифеста.

Области действия Oauth

Для работы надстройки диска пользователь должен предоставить разрешение на доступ. Список разрешений известен как Области действия. Подробную информацию о прицелах для конкретных дисков можно найти здесь. Нам нужно снова предоставить области в файле appsscript.json в виде списка с oauthScopes.

Примечание. Если ваш файл appsscript.json скрыт, перейдите в настройки и установите флажок «Показать файл манифеста «appsscript.json» в редакторе».

Ознакомьтесь с файлом манифеста для этого проекта ниже.

{
 "timeZone": "Asia/Kathmandu",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/script.storage",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/drive.file",
    "https://www.googleapis.com/auth/drive.addons.metadata.readonly"
  ],
  "addOns": {
    "common": {
      "name": "Drive Extension with Apps Script",
      "logoUrl": "provide image URL to be used as logo",
      "layoutProperties": {
        "primaryColor": "#41f470",
        "secondaryColor": "#ab2699"
      }
    },
    "drive": {
      "homepageTrigger": {
        "runFunction": "onDriveHomePageOpen",
        "enabled": true
      },
      "onItemsSelectedTrigger": {
        "runFunction": "onDriveItemsSelected"
      }
    }
  }
}

Использование скрипта приложений для доступа к Google Диску

Теперь в корневой папке проекта создайте два файла, карты и основной.

Назначение функций сценариев приложений триггерам

основной

// On homepage trigger function
let onDriveHomePageOpen = () => homepageCard();
// On Item selected Trigger function
let onDriveItemsSelected = (e) => itemSelectedCard(e);

onDriveHomePageOpen и onDriveItemsSelected — это две функции, которые мы ранее назначили в файле манифеста. Эти функции, в свою очередь, вызывают другие функции, которые мы создадим позже. Если при сохранении файла появляется всплывающее окно с ошибкой, отклоните его на данный момент.

Проектирование поведения карт

Давайте создадим простую карточку домашней страницы, которая будет назначена нашему неконтекстному триггеру в файле карточек.

Создать карточку домашней страницы

let homepageCard = () => {
// Create a card with a header section
  let card = CardService.newCardBuilder().setHeader(CardService.newCardHeader());
// create card section 
  let section = CardService.newCardSection();
// add heading 
  let decoratedText = CardService.newDecoratedText()
    .setText("Select Files To Update");
// add text as a widget to the card section
  section.addWidget(decoratedText);
// add the section to the card 
  card.addSection(section);
// return card as build
  return card.build();
}

Карты можно использовать для создания пользовательского интерфейса надстроек для гугл диска.

Это блог для начинающих, поэтому я не занимаюсь стилем.

Создать неконтекстную карточку

Теперь давайте возьмем еще одну карту, которая будет отвечать за контекстный триггер в том же файле. Но давайте разделим этот код на секции, чтобы было понятно.

1. Создайте простой интерфейс карточки.

let itemSelectedCard = (e) => {
  // Initial UI
  let card = CardService.newCardBuilder().setHeader(CardService.newCardHeader().setTitle("Select Sheets Update Master Sheet"));
  let filesSection = CardService.newCardSection()
  filesSection.setHeader("Selected Files");
  return card.build();
}

2. Отображать выбранные файлы в пользовательском интерфейсе.

let itemSelectedCard = (e) => {
  // Initial UI
  let card = CardService.newCardBuilder().setHeader(CardService.newCardHeader().setTitle("Select Sheets Update Master Sheet"));
  let filesSection = CardService.newCardSection()
  filesSection.setHeader("Selected Files");
 // New Code starts here 
// # 1
// Create a new array to hold selected files and data
  let selectedSheets = [];
// #2
// Fetch selected files data from drive through event objects
  if (e.drive.selectedItems.length > 0) {
    // Selected spreadsheets
// #3
// Among the selected items we'll be selecting only spreadsheets and push them to selected sheets
    e.drive.selectedItems.forEach(item => {
      if (item.mimeType === "application/vnd.google-apps.spreadsheet")
        selectedSheets.push(item)
    }
    );
  }
  // Create a counter to count the number of widgets added
// #4
// COunter is required to prevent error when pushing the file names into UI incase array is empty
  let widgetCounter = 0;
  for (let i = 0; i < selectedSheets.length; i++) {
    // #5
    // Create decorated text with selected files and 
    // add the decorated text to the card section
    filesSection.addWidget(CardService.newDecoratedText()
      //.setText(selectedSheets[i].title)
      .setText(e.drive.selectedItems[0].title)
    );
 // Increase widget counter per loop
 // #4
    widgetCounter += 1;
  }
  // #6
  // Add files as widgets only if widgetCounter is 1+
  //  It prevent error in case only non-spreadsheet files are selected 
  if (widgetCounter >= 1) {
    card.addSection(filesSection)
 }
  // Create Another card that has files list 
  return card.build();
}

Здесь (см. код для нумерации, такой как № 1),

  1. Создан массив для хранения данных о выбранных элементах.
  2. Используется объект события привода для получения данных о выбранных файлах.
  3. Среди выбранных элементов мы отфильтровали только электронные таблицы с использованием mimeType.
  4. Мы создали счетчик для использования в качестве условия при добавлении файлов в качестве виджетов на карту, чтобы предотвратить ошибки.
  5. Создан оформленный текст, виджет, в котором будут храниться имена файлов каждого файла.
  6. Теперь, наконец, добавил весь раздел файлов в сборщик карт.

Создать действия с помощью кнопки

В Карте интерактивность возможна с помощью действий. Также ознакомьтесь с этим примером кода. Не забудьте добавить указанную там область действия, чтобы управлять областью действия в файле манифеста.

Давайте добавим кнопки под разделом файлов. Эта кнопка соберет выбранные файлы и передаст их на другую карту, которую мы создадим позже. Чтобы не усложнять, я разобью код на более мелкие части.

1. Создайте пользовательский интерфейс кнопки с действием

let nxtButtonSection = CardService.newCardSection();
  let nxtButtonAction = CardService.newAction()
    .setFunctionName("handleNextButtonClick");

Вы заметили, что handleNextButtonClick был назначен функцией, которая будет запускаться при нажатии кнопки. Он будет обрабатывать навигацию и указывает на следующую карту. Мы создадим эту функцию позже.

2. Назначить параметры для передачи.

// We'll pass only pass ids of files to the next card so that we can fetch them there with id
// #1
  let selectedSheetsIDAsStr = selectedSheets.map(item => item.id).join();
// pass the values as params
// #2
  nxtButtonAction.setParameters({
    "nextCard": "nextCard",
    "selectedSheetsIDAsStr": selectedSheetsIDAsStr,
  });
// add button to the button set 
// #3
  let nxtButton = CardService.newTextButton().setText("Next").setOnClickAction(nxtButtonAction);
  let nxtButtonSet = CardService.newButtonSet().addButton(nxtButton);

В карточке параметры нужно передавать через действие с методом setParameters как объекты (#2). Важно помнить, что и ключи, и значения должны быть строковыми (отсюда №1). Кнопки можно добавлять в виде набора кнопок в карточке (#3).

Вы заметили, что nextCard был назначен в качестве параметра. Это связано с тем, что функция handleNextButtonClick является общей функцией, которая принимает имя карты в качестве параметра, а не жестко кодирует ее. Таким образом, он будет более эффективным в долгосрочной перспективе.

Добавить кнопку на карточку

//  It prevent error in case only non-spreadsheet files are selected 
  if (widgetCounter >= 1) {
    card.addSection(filesSection)
    // new line
    nxtButtonSection.addWidget(nxtButtonSet);
    card.addSection(nxtButtonSection);
  }

Карточная навигация

Из того, что я понял, навигация по картам, короче, берет список карт как стопку. Новая карта для перехода добавляется в верхнюю часть стопки, а извлекается из стопки, чтобы вернуться к предыдущей.

Давайте создадим новый файл, я назову его helpers и добавим следующие инструкции.

помощники

/* This is a greneral nav function
You use it with card action and as a response, it will supply card functions from cardsInventory */
let handleNextButtonClick = (e) => {
// #1
// Extract string nextCard to pass it as key in cards inventory obj
  let nxtCard = cardsInventory[e.commonEventObject.parameters.nextCard];
  // #2
  // Convert String into List of files selected by the user
  let selectFilesIdList = e.commonEventObject.parameters['selectedSheetsIDAsStr'].split(",");
// #3
// use actionResponse to create a navigation route to the next card
  let nxtActionResponse = CardService.newActionResponseBuilder()
    .setNavigation(CardService.newNavigation().pushCard(nxtCard(selectFilesIdList))) // #4, Passing the mastersheet with params
    .setStateChanged(true)
    .build();
  return nxtActionResponse;
}

/**
 *  Create a dictionary that
 is consist of cards for navigation with appropriate keys  
 */
var cardsInventory = {
  'nextCard': nextCard
}

Давайте сначала поговорим об объектеcardsInventory. Если вы помните, ранее мы передавали параметр nextCard как строку в функцию itemSelectedCard. Эта nextCard — функция, которую мы создадим следующей. Но дело в том, что вы не можете передать строку и использовать ее для ссылки на переменную (проверьте #1 в коде). Итак, мы создаем словарь, который будет сопоставлять соответствующие клавиши с функциями для навигации.

Внутри функции handleNextButtonClick:

  1. Извлеките строку, которая является ключом к объекту cardInventory, чтобы выбрать правильную карту для вызова. Мы используем Events Comment Object для извлечения ранее переданных параметров.
  2. Строка, которая была передана как идентификаторы выбранных файлов, мы снова конвертируем в массив.
  3. Комбинация NewActionResponseBuilder, SetNavigation, NewNavigation и PushCard используется для установки нового пути к карте по нашему выбору.
  4. Здесь мы передаем список идентификаторов в качестве параметров.

Следующая карта для навигации

Мы создадим очень простую карточку, которой будет достаточно, чтобы отобразить список идентификаторов, чтобы мы знали, что наш код работает.

Во-первых, давайте создадим новый файл next_card.

var nextCard = function (lst) {
  let cardService = CardService.newCardBuilder().setHeader(CardService.newCardHeader().setTitle("Select Master Sheet To Update"));
  let filesSection = CardService.newCardSection();
  filesSection.setHeader("Selected Files");
  let widgetCounter = 0;
  let selectedFilesList = [...lst];
  selectedFilesList.forEach(id => {
    filesSection.addWidget(CardService.newDecoratedText()
      .setText(id));
    widgetCounter += 1;
  });
  if (widgetCounter >= 1) {
    cardService.addSection(filesSection);
  }

  return cardService.build();
}

Единственное новое, на что здесь следует обратить внимание, это то, что я не использую синтаксис es6 для объявления функции. Это связано с тем, что его использование вызвало проблему с областью действия и ошибку функция не определена. Поэтому я пошел в старую школу с var.

Опубликовать надстройку в GCP для тестирования

Чтобы опубликовать дополнение к GCP, следуйте этим двум инструкциям здесь.

  1. Создайте стандартный Проект GCP.
  2. Интегрируйте проект с проектом сценариев приложений.

Окончательный код

карточки

let itemSelectedCard = (e) => {
  // Initial UI
  let card = CardService.newCardBuilder().setHeader(CardService.newCardHeader().setTitle("Select Sheets Update Master Sheet"));
  let filesSection = CardService.newCardSection()
  filesSection.setHeader("Selected Files");
  let nxtButtonSection = CardService.newCardSection();
  let nxtButtonAction = CardService.newAction()
    .setFunctionName("handleNextButtonClick");
  let selectedSheets = [];
  if (e.drive.selectedItems.length > 0) {
    // hostApp,clientPlatform,drive,commonEventObject
    // Selected spreadsheets
    e.drive.selectedItems.forEach(item => {
      if (item.mimeType === "application/vnd.google-apps.spreadsheet")
        selectedSheets.push(item)
    }
    );
  }
  // Create a counter to count number of widgets added
  let widgetCounter = 0;
  for (let i = 0; i < selectedSheets.length; i++) {
    // Create decorated text with selected files and 
    // add the decorated text to card section
    filesSection.addWidget(CardService.newDecoratedText()
      //.setText(selectedSheets[i].title)
      .setText(e.drive.selectedItems[0].title)
    );
    widgetCounter += 1;
  }

  // Change list of  selected sheet's id  as string to pass to next card 
  let selectedSheetsIDAsStr = selectedSheets.map(item => item.id).join();
  nxtButtonAction.setParameters({
    "nextCard": "nextCard",
    "selectedSheetsIDAsStr": selectedSheetsIDAsStr,
  });
  let nxtButton = CardService.newTextButton().setText("Next").setOnClickAction(nxtButtonAction);
  let nxtButtonSet = CardService.newButtonSet().addButton(nxtButton);

  // Add files and button section only if the widgets are present
  //  It prevent error in case only non-spreadsheet files are selected 
  if (widgetCounter >= 1) {
    card.addSection(filesSection)
    nxtButtonSection.addWidget(nxtButtonSet);
    card.addSection(nxtButtonSection);
  }
  // Create Another card that has files list 
  return card.build();
}

помощники

/* THis is a greneral nav function
You use it with card action and as reponse it will supply card functions from cardsInventory */
let handleNextButtonClick = (e) => {
  let nextCard = cardsInventory[e.commonEventObject.parameters.nextCard];
  console.log(nextCard)
  // Convert String into List
  let selectFilesIdList = e.commonEventObject.parameters['selectedSheetsIDAsStr'].split(",");
  let nxtActionResponse = CardService.newActionResponseBuilder()
    .setNavigation(CardService.newNavigation().pushCard(nextCard(selectFilesIdList)))
    .setStateChanged(true)
    .build();
  return nxtActionResponse;
}

/**
 *  Create a dictionary that
 is consist of cards for navigation with appropriate keys  
 */
var cardsInventory = {
  'nextCard': nextCard
}

Краткое содержание

Хорошо, давайте вспомним, что мы делали в проекте.

  1. Определены файлы appscrits.json с соответствующими областями действия и триггерами, необходимыми для надстройки Drive.
  2. Создан простой интерфейс карты для взаимодействия с пользователями.
  3. Извлечены выбранные файлы с диска с помощью скрипта приложений.
  4. Используемые наборы действий и кнопок позволяют пользователям взаимодействовать с пользовательским интерфейсом нашей карты.
  5. Создана простая логика навигации для перемещения между двумя картами.

Покажите некоторую поддержку

Это Нибеш Хадка из салона кодирования Хадки. Другие мои блоги о скриптах Google Apps можно найти здесь. Я владелец Khadka’s Coding Lounge. Мы делаем веб-сайты, мобильные приложения, надстройки Google и ценные технические блоги. Наймите нас! Ставьте лайк, делитесь и подписывайтесь на нашу рассылку.