Google Docs Add-on. Расширяем возможности редактора Google Docs

от автора

На днях Google анонсировал выход новой платформы, позволяющей разработчикам создавать приложения, работающие внутри Google Docs и расширяющие базовый функционал Google Docs редактора.
Разберемся что это, как это работает и напишем небольшое приложение которое позволит нам переводить текст документа не выходя из Google Docs.

Немного теории.

Google Docs Аdd-on — приложение, написанное на javascript’е, существующее и работающее в Google Drive (сейчас пока только для Google Docs), имеющее свой UI (‘sidebar’ справа от документа или модальное окно поверх), расширяющее функционал Google Docs редактора. Google Docs Аdd-on является следующим шагом в развитии Google Apps Script. По сути это Google Apps Script проект с возможностью распространения и установки его другими пользователями Google Docs.

Существует несколько вида Google Apps Script проектов, основные из них — отдельный файл-проект в Google Drive (В Google Drive вы видите его как отдельный файл, ‘standalone script’) или проект с документом-контейнером (‘container-bounds script’). Его вы можете увидеть открыв документ-контейнер, меню ‘Tools’ -> ‘Script editor’. Google Docs Аdd-on — как раз второй вариант, для его создания необходим документ-контейнер, который вы будете использовать для тестирования и отладки вашего приложения.

Распространение

Распространяются Google Docs Аdd-on через Chrome Web store (хотя и не являются расширением браузера). Перед добавлением вашего add-on’a в Chrome Web Store необходимо пройти review со стороны Google. Для пользователей Google Docs все add-on’ы доступны для установки через новый пункт меню — ‘Add-ons’ -> ‘Get Add-ons’.

Среда разработки

Для Google Apps Script проектов Google предоставляет среду разработки, интегрированную в Google Drive, с возможностью запуска, отладки и автодополнения кода. Есть ли возможность использовать другую среду разработки и хранить код не в Google Drive, а в CVS? Это позволяло бы работать над проектом нескольким разработчикам одновременно и поддерживать версионирование. Для ‘standalone’ проектов такая возможность есть, для ‘сontainer-bounds’ — к сожалению пока нет. Поэтому для разработки Google Docs Add-on’a приходится использовать интегрированную среду разработки. Вот пример python скрипта, который позволяет «выкачать» ‘standalone’ проект на файловую систему и обновить его в Google Drive после изменения.

Что внутри

Немного подробнее о том, как работает Google Apps Script. Файлы проекта содержат «серверную» (.gs) и «клиентскую» (.html) части приложения. «Серверная» часть — набор javascript функций, которые могут обращаться к сторонним сервисам, различным Google API (Drive, GMail, Calendar), в том числе к API документа, в котором установлен данный add-on.
Также ‘.gs’ файлы содержат предопределенные функции, которые вызываются когда пользователь устанавливает/открывает add-on, что позволяет создавать UI вашего приложения внутри стандартного редактора Google Docs. В свою очередь «клиесткая часть» представляет собой стандартные html файлы, с возможностью вызова любой функции из любого «серверного» файла. А также, естественно, с возможностью подключения сторонних CSS/javascript файлов.

Пример

Напишем приложение, которое будет переводить выделенный текст в документе с английского на русский язык. Создаем новый Google Docs документ в Google Drive, открываем меню ‘Tools’ -> ‘Script editor’. В появившемся диалоге выбираем ‘Create script for -> Document’. После этого вы увидите ту самую интегрированную среду разработки. В проекте по умолчанию создается файл ‘Code.gs’, содержащий пример приложения от Google. Нам он пока не нужен, поэтому я сразу предлагаю очистить его содержимое, а сам файл переименовать в ‘Server.gs’. Также лучше сразу переименовать проект из ‘Untitled project’, скажем в ‘Translate example’.

Итак, в ‘Server.gs’ мы определим функции, которые будут создавать UI нашего add-on’a. Для этого достаточно объявить функцию ‘onOpen’.

function onOpen() {   DocumentApp.getUi().createAddonMenu()     .addItem('Translate', 'openSidebar')     .addToUi(); }  function openSidebar( ) {} 

Теперь, при открытии документа, в котором установлен данный add-on, в специальном меню ‘Add-ons’ появиться пункт ‘Translate’. Нажатие на данный пункт меню будет вызывать функцию ‘openSidebar’, которая пока ничего не делает. Чтобы проверить это, в среде разработки выберите функцию «onOpen» и нажмите ‘Run’

Теперь в вашем документе появился пункт меню ‘Add-ons’ -> ‘Translate example’ (название вашего проекта) -> ‘Translate’

Займемся разработкой UI для нашего add-on’a. Так как UI add-on’a представляет собой html страницу, то есть смысл сразу разделить HTML разметку, CSS и javascript код на разные файлы. Для этого мы создадим файлы ‘Sidebar.html’, ‘Styles.html’, ‘Scripts.html’ и ‘Content.html’. ‘Sidebar.html’ будет файлом-темплейтом, включающем в себя остальные файлы.

UI всех add-on’ов должен следовать определенному style guide, поэтому Google предоставляет специальный CSS файл с предопределенными стилями, ссылку на который вы видите в примере.

Открываем ‘Content.html’ и ‘Styles.html’ и создаем разметку

<div class="content">   <p>Select text and click 'Translate'</p>   <button class="action btn-block">Translate</button>   <p class="result"></p> </div> 

и стили

<style>   .content {     padding: 20px;     margin : 20px;   }    .btn-block {      display: block;      width: 100%;      padding-left: 0;      padding-right: 0;   } </style> 

а в ‘Server.gs’ дописываем тело функции ‘openSidebar’

function onOpen() {   DocumentApp.getUi().createAddonMenu()     .addItem('Translate', 'openSidebar')     .addToUi(); }  function openSidebar( ) {   var html = HtmlService.createTemplateFromFile('Sidebar')     .evaluate()     .setSandboxMode(HtmlService.SandboxMode.NATIVE)     .setTitle('Translate example')     .setWidth(300);   DocumentApp.getUi().showSidebar(html); } 

Теперь по нажатию пункт меню «Translate» в документе, слева, откроется ‘sidebar’.

Осталось добавить логику для перевода текста. Для этого мы будем использовать API различных Google сервисов, доступных нам в ‘.gs’ файлах и позволяющих находить выделенный в документе текст и переводить его на выбранный язык.
‘Server.gs’

function translate(pollingCounter) {   var text = getSelectedText(getSingleSeletetedElement());   if (text) {     return LanguageApp.translate(text, 'en', 'ru');   } }  function getSelectedText(rangeElement) {   if (!rangeElement) { return; }      var element = rangeElement.getElement();   var from = rangeElement.getStartOffset();   var to = rangeElement.getEndOffsetInclusive() + 1;   if (element.getType() === DocumentApp.ElementType.TEXT || element.getType() === DocumentApp.ElementType.PARAGRAPH) {     var text = element.getText();     return text.substring(from, to);   } }  function getSingleSeletetedElement() {   var selection = DocumentApp.getActiveDocument().getSelection();   if (selection) {     var elements = selection.getSelectedElements();     if (elements.length === 1) {       return elements[0];     }   } } 

Функция ‘translate’ ищет выделенный элемент в документе, и если это текст или текстовый блок — получает выделенную часть текста и переводит ее с английского на русский язык. Теперь нам нужно как-то вызвать эту функцию из нашего UI. Для этого javascript код, запущенный в ‘sidebar’ имеют доступ к глобальному объекту ‘google.script.run’, позволяющему вызвать любую функцию из любого ‘.gs’ файла.
‘Scripts.html’

  $(function() {          $('.action').click(function() {        google.script.run.withSuccessHandler(function(text) {          if (text) {            $('.result').text('Result: ' + text);          } else {            $('.result').text('Please, select text to translate');          }        }).translate();     });      }); 

Готово, теперь наш add-on может переводить выделенный текст по нажатию на клавишу ‘Translate’.

С ходу можно внести 2 улучшения. Во-первых, при открытии ‘sidebar’, пока загружаются все необходимые для него файлы, было бы неплохо добавить какой-нибудь индикатор загрузки. Создадим файл ‘Loading.html’, добавим ссылку на него в наш ‘Sidebar.html’, а основное содержимое спрячем до полной загрузки всех ресурсов. Как только все готово — мы прячем индикатор и показываем наш UI.
‘Loading.html’

<div class="app-loading" style="text-align:center">       <br><br><br><br><br><br>             Loading... </div> 

Content.html

<div class="content" style="display:none">   <p>Select text and click 'Translate'</p>   <button class="action btn-block">Translate</button>   <p class="result"></p> </div> 

Sidebar.html

<!-- styles --> <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css"> <?!= HtmlService.createHtmlOutputFromFile('Styles').getContent(); ?>  <!-- layout --> <?!= HtmlService.createHtmlOutputFromFile('Loading').getContent(); ?> <?!= HtmlService.createHtmlOutputFromFile('Content').getContent(); ?>  <!--3rd party scripts --> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>  <!-- application scripts --> <?!= HtmlService.createHtmlOutputFromFile('Scripts').getContent(); ?> 

и наконец в Scripts.html

  $(function() {     $('.app-loading').hide();     $('.content').show();     $('.action').click(function() {        google.script.run.withSuccessHandler(function(text) {          if (text) {            $('.result').text('Result: ' + text);          } else {            $('.result').text('Please, select text to translate');          }        }).translate();     });      }); 

А во-вторых, было бы неплохо автоматически получать перевод выделенного текста из документа, без нажатия кнопки ‘Translate’. Для решения этой задачи нужно придумать как обойти одно из ограничений, существующее сейчас в Google Docs Аdd-on — нет event модели, которая позволяла бы Google Script Аdd-on’у реагировать на действия пользователя в документе. Нам прийдется имитировать интерактивность add-on’a используя long polling — постоянные запросы со стороны UI, которые будут вынуждать наш ‘ Server.gs’ проверять документ на наличие выделения и переводить текст. Для этого напишем в ‘Script.html’ функцию, которая будет запускать 2 процесса с определенным интервалом. Первый процесс будет вызывать ‘translate’ функцию и просто сохранять результат перевода. Второй процесс будет проверять на наличие результат перевода и обновлять UI.
‘Scripts.html’

  var LongPollingManager = (function() {      var process = function(options) {              var pollingTimeout = 1500,         notificationTimeout = 1500,         pollingCounter = 0,         pollingStopped = false,         pollingResult,         pollingProcess,         notificationProcess;         var start = function() {         pollingStopped = false;         pollingProcess = setInterval(function() {           if (pollingStopped) { return; }           pollingCounter++;           google.script.run.withSuccessHandler(function(response) {             if (pollingStopped) { return; }             if (!pollingResult || response.pollingCounter > pollingResult.pollingCounter) {               pollingResult = response;             }           })[options.method](pollingCounter);         }, pollingTimeout);                   setTimeout(function() {           notificationProcess = setInterval(function() {             if (pollingStopped) { return; }             options.notification(getLastResult());           }, notificationTimeout);         }, notificationTimeout/2);                 };        var getLastResult = function() {         return pollingResult;       };        var stop = function() {         pollingResult = null;         pollingStopped = true;         clearInterval(pollingProcess);         clearInterval(notificationProcess);       };                                return {         start: start,         stop : stop,         isActive: function() { return !pollingStopped; },         getLastResult : getLastResult       };;     };          return {       process: process     };    })();      var showResult = function(result) {     if (result && result.text) {       $('.result').text('Result: ' + result.text);      } else {        $('.result').text('');      }   };      $(function() {     $('.loading').hide();     $('.content').show();          $('.action').click(function() {        google.script.run.withSuccessHandler(function(text) {          if (text) {            $('.result').text('Result: ' + text);          } else {            $('.result').text('Please, select text to translate');          }        }).translate();     });          LongPollingManager.process({       method: 'translate',       notification: showResult     }).start();       }); 

‘Server.gs’

function translate(pollingCounter) {   var result = { pollingCounter : pollingCounter };   var text = getSelectedText(getSingleSeletetedElement());   if (text) {     result.text = LanguageApp.translate(text, 'en', 'ru');   }   return result; } 

Готово, теперь наш add-on автоматически переводит выделенный текст.
Документ + код проекта на Google Drive
Код на github

Заключение

Итак, Google Docs add-on — новая платформа, позволяющая создавать приложения, расширяющие возможности базового редактора Google Docs.
Плюсы и минусы, на мой взгляд:

  • + Огромная аудитория Google Docs пользователей.
  • + Разработка приложения полностью на javascript.
  • + Доступ к многочисленным Google API и любому стороннему сервису (HTTP запросы).
  • — Разработка проекта внутри Google Docs, нет возможности вести разработку локально, трудно разделить работу между несколькими разработчиками.
  • — Отсутствие интерактивного взаимодействия с документом (нет возможности получать оповещения о каких-либо действия пользователя в документе.

P. S.

Я думаю что в соперничестве MS Office — Google Drive, Google решил сделать ставку на простоту, удобство и сообщество разработчиков. Поясню на примере MS Word vs Goodle Docs. MS Word имеет огромное количество доступных пользователю функций (и как следствие — перегруженный и сложный UI) и не имеет открытого API. Google Docs — наоборот, обладает минимально необходимым набором инструментов и открытым API. Теперь любую недостающую вам функцию вы можете найти в галлерее Google Docs add-on’ов или написать сами. А пользователь может добавить в свой документ только нужные ему функции.

ссылка на оригинал статьи http://habrahabr.ru/post/215843/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *